/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2009 by Trifork
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package erjang;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.List;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public class EString extends ESeq implements CharSequence {
private static final Charset ISO_LATIN_1 = Charset.forName("ISO-8859-1");
private static final EString EMPTY = new EString("");
final byte[] data;
final int off;
private int hash = -1;
public EString(String value) {
this.hash = value.hashCode();
this.data = value.getBytes(ISO_LATIN_1);
this.off = 0;
}
public EString testString() {
return this;
}
EString(byte[] data, int off) {
this.data = data;
this.off = off;
}
EString(byte[] data, int off, int len) {
if (data.length == off+len) {
this.data = data;
this.off = off;
} else {
this.data = new byte[len];
this.off = 0;
System.arraycopy(data, off, this.data, 0, len);
}
}
/**
* @param array
* @param arrayOffset
* @param len
*/
public static EString make(byte[] array, int arrayOffset, int len) {
if (len == array.length - arrayOffset) {
return new EString(array, arrayOffset);
} else {
byte[] copy = new byte[len];
System.arraycopy(array, arrayOffset, copy, 0, len);
return new EString(copy, 0);
}
}
/**
* @param list
*/
public static ESeq make(ECons list) {
EString s;
if ((s = list.testString()) != null) {
return s;
} else if (list.isNil()) {
return EString.EMPTY;
} else {
ByteArrayOutputStream barr = new ByteArrayOutputStream();
EObject tail = list;
while ((list = tail.testNonEmptyList()) != null) {
EObject head = list.head();
ESmall intval;
if ((intval = head.testSmall()) == null) {
throw ERT.badarg();
}
int byteValue = intval.value & 0xff;
if (intval.value != byteValue) {
throw ERT.badarg();
}
barr.write(byteValue);
tail = list.tail();
}
return new EString(barr.toByteArray(), 0);
}
}
@Override
public int hashCode() {
if (hash == -1) {
hash = stringValue().hashCode();
}
return hash;
}
public String stringValue() {
return new String(data, off, data.length - off, ISO_LATIN_1);
}
public boolean equalsExactly(EObject rhs) {
int length = length();
if ((rhs.testNil()) != null) {
return length == 0;
}
EString str;
if ((str = rhs.testString()) != null) {
EString es = str;
if (length != es.length())
return false;
for (int i = 0; i < length; i++) {
if (charAt(i) != es.charAt(i))
return false;
}
return true;
}
ESeq seq;
if ((seq = rhs.testSeq()) != null) {
int i = 0;
while (i < length) {
if (seq.testNil() != null)
return false;
if (!seq.head().equalsExactly(new ESmall(charAt(i)))) {
return false;
}
seq = seq.tail();
}
}
return false;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof String) {
return ((String) obj).equals(stringValue());
}
if (!(obj instanceof EObject)) {
return false;
}
return equalsExactly((EObject) obj);
}
@Override
public char charAt(int index) {
return (char) (data[off + index] & 0xff);
}
@Override
public int length() {
return data.length - off;
}
@Override
public CharSequence subSequence(final int start, final int end) {
if (end == length())
return new EString(data, off + start);
return new SubSequence(start, end - start);
}
public class SubSequence implements CharSequence {
private final int offset;
private final int length;
public SubSequence(int start, int length) {
this.offset = start;
this.length = length;
EString.this.check_subseq(offset, length);
}
@Override
public char charAt(int index) {
return EString.this.charAt(offset + index);
}
@Override
public int length() {
return length;
}
@Override
public CharSequence subSequence(int start, int end) {
return new SubSequence(this.offset + start, end - start);
}
}
void check_subseq(int offset, int length) {
if (offset < 0 || length < 0 || (offset + length) > length())
throw new IllegalArgumentException();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("\"");
for (int i = 0; i < length(); i++) {
char c = charAt(i);
appendChar(sb, c);
}
sb.append('"');
return sb.toString();
}
static void appendChar(StringBuilder sb, char c) {
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\') {
sb.append('\\');
switch(c) {
case '\t': sb.append('t'); break;
case '\r': sb.append('r'); break;
case '\n': sb.append('n'); break;
case '\b': sb.append('b'); break;
case '"': sb.append('"'); break;
case '\\': sb.append('\\'); break;
default:
sb.append('x');
if (c < 0x10) sb.append('0');
sb.append(Integer.toHexString(c).toUpperCase());
}
} else {
sb.append(c);
}
}
public static EString fromString(String s) {
return new EString(s);
}
private static final Type ESTRING_TYPE = Type.getType(EString.class);
private static final Type STRING_TYPE = Type.getType(String.class);
@Override
public Type emit_const(MethodVisitor fa) {
Type type = ESTRING_TYPE;
fa.visitLdcInsn(this.stringValue());
fa.visitMethodInsn(Opcodes.INVOKESTATIC, type.getInternalName(),
"fromString", "(" + STRING_TYPE.getDescriptor() + ")"
+ type.getDescriptor());
return type;
}
/*
* (non-Javadoc)
*
* @see erjang.ESeq#cons(erjang.EObject)
*/
@Override
public ESeq cons(EObject h) {
// ESmall sm;
// if ((sm=h.testSmall()) != null && ((sm.value & ~0xff) == 0)) {
// return new EStringList((byte)sm.value, this);
// }
return new EList(h, this);
}
/*
* (non-Javadoc)
*
* @see erjang.ESeq#tail()
*/
@Override
public ESeq tail() {
if (off == data.length-1)
return ERT.NIL;
return new EString(data, off + 1);
}
/*
* (non-Javadoc)
*
* @see erjang.ECons#head()
*/
@Override
public ESmall head() {
return ESmall.little[(data[off] & 0xff)];
}
@Override
public ENil testNil() {
return length() == 0 ? ERT.NIL : null;
}
@Override
public ESeq testSeq() {
return this;
}
public ECons testNonEmptyList() {
return length() == 0 ? null : this;
}
public ECons testCons() {
return this;
}
/*
* (non-Javadoc)
*
* @see erjang.ECons#compare_same(erjang.EObject)
*/
@Override
int compare_same(EObject rhs) {
if (rhs.isNil())
return isNil()? 0 : 1;
int length = length();
EString str;
if ((str = rhs.testString()) != null) {
EString es = str;
int length2 = str.length();
int limit = Math.min(length, length2);
for (int i = 0; i < limit; i++) {
char ch1 = charAt(i);
char ch2 = es.charAt(i);
if (ch1 < ch2)
return -1;
if (ch1 > ch2)
return 1;
}
if (length > length2)
return 1;
if (length < length2)
return -1;
return 0;
}
ECons rseq;
if ((rseq = rhs.testCons()) != null) {
int i = 0;
while (i < length) {
if ((rseq.testNil()) != null) {
return 1; // I AM LONGER
}
int cmp = (new ESmall(charAt(i++))).erlangCompareTo(rseq.head());
if (cmp != 0)
return cmp;
EObject res = rseq.tail();
if ((rseq = res.testCons()) != null) {
continue;
}
return -res.erlangCompareTo(new EString(data, i));
}
}
return -rhs.erlangCompareTo(this);
}
/*
* (non-Javadoc)
*
* @see erjang.ECons#prepend(erjang.ECons)
*/
@Override
public ESeq prepend(ESeq list) {
EString other = list.testString();
if (other != null) {
byte[] out = new byte[length() + other.length()];
System.arraycopy(other.data, other.off, out, 0, other.length());
System.arraycopy(this.data, this.off, out, other.length(), this
.length());
return EString.make(out, 0, out.length);
} else {
return super.prepend(list);
}
}
/**
* @return
*/
public EBinary asBitString() {
return new EBinary(data, off, length());
}
public static EString make(CharSequence seq, int start, int end) {
return fromString( seq.subSequence(start, end).toString() );
}
/**
* @param eObject
* @return
*/
public static ESeq make(EObject eObject) {
ESeq str;
if ((str = eObject.testSeq()) != null)
return make(str);
EAtom am = eObject.testAtom();
if (am != null) {
return fromString(am.toString());
}
throw ERT.badarg();
}
/**
* @param bin
* @return
*/
public static EString make(EBinary bin) {
return new EString(bin.data, bin.byteOffset(), bin.byteSize());
}
public static EString make(EBinary bin, int offset, int length) {
return new EString(bin.data, bin.byteOffset()+offset, length);
}
/**
* @return the contents of this string as a java.nio.ByteBuffer
*/
public boolean collectIOList(List<ByteBuffer> out) {
if (length() != 0) {
out.add(ByteBuffer.wrap(data, off, data.length - off));
}
return true;
}
@Override
public void visitIOList(EIOListVisitor out) throws ErlangError {
out.visit(data, off, data.length - off);
}
public ESeq collectCharList(CharCollector out, ESeq rest)
throws CharCollector.CollectingException, IOException
{
try {
return out.addIntegers(data, off, data.length - off, rest);
} catch (CharCollector.PartialDecodingException e) {
int n = e.inputPos;
throw new CharCollector.CollectingException(new EString(data, off+n));
}
}
/**
* @param buf
* @return
*/
public static ESeq make(ByteBuffer data) {
if (data == null || data.remaining() == 0)
return ERT.NIL;
return EString.make(data.array(), data.arrayOffset() + data.position(),
data.remaining());
}
/**
* @param strbuf
* @return
*/
public static EString make(byte[] strbuf) {
return make(strbuf, 0, strbuf.length);
}
/**
* @param i
* @return
*/
public static boolean isValidCodePoint(int cp) {
return (cp >>> 16) <= 0x10 // in 0..10FFFF; Unicode range
&& (cp & ~0x7FF) != 0xD800 // not in D800..DFFF; surrogate range
&& (cp & ~1) != 0xFFFE; // not in FFFE..FFFF; non-characters
}
public static ESeq read(EInputStream ei) throws IOException {
return ei.read_string();
}
public static int[] stringToCodePoints(final String s) {
final int m = s.codePointCount(0, s.length());
final int [] codePoints = new int[m];
for (int i = 0, j = 0; j < m; i = s.offsetByCodePoints(i, 1), j++) {
codePoints[j] = s.codePointAt(i);
}
return codePoints;
}
@Override
public void encode(EOutputStream eos) {
eos.write_string(stringValue());
}
}