// This file is part of PleoCommand:
// Interactively control Pleo with psychobiological parameters
//
// Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program 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. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Boston, USA.
package pleocmd.pipe.val;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.io.UnsupportedEncodingException;
import pleocmd.exc.InternalException;
public final class StringValue extends Value {
static final char TYPE_CHAR = 'S';
static final ValueType RECOMMENDED_TYPE = ValueType.NullTermString;
private static final int[] ASCII_TABLE = { //
/**//**/0, 0, 0, 0, 0, 0, 0, 0, // 00 - 07
0, 0, 0, 0, 0, 0, 0, 0, // 08 - 0F
0, 0, 0, 0, 0, 0, 0, 0, // 10 - 17
0, 0, 0, 0, 0, 0, 0, 0, // 18 - 1F
2, 1, 1, 1, 1, 1, 1, 1, // 20 - 27
1, 1, 1, 1, 1, 1, 1, 1, // 28 - 2F
1, 1, 1, 1, 1, 1, 1, 1, // 30 - 37
1, 1, 3, 1, 1, 1, 1, 1, // 38 - 3F
1, 1, 1, 1, 1, 1, 1, 1, // 40 - 47
1, 1, 1, 1, 1, 1, 1, 1, // 48 - 4F
1, 1, 1, 1, 1, 1, 1, 1, // 50 - 57
1, 1, 1, 1, 1, 1, 1, 1, // 58 - 5F
1, 1, 1, 1, 1, 1, 1, 1, // 60 - 67
1, 1, 1, 1, 1, 1, 1, 1, // 68 - 6F
1, 1, 1, 1, 1, 1, 1, 1, // 70 - 77
1, 1, 1, 1, 0, 1, 1, 0, // 78 - 7F
0, 0, 0, 0, 0, 0, 0, 0, // 80 - 87
0, 0, 0, 0, 0, 0, 0, 0, // 88 - 8F
0, 0, 0, 0, 0, 0, 0, 0, // 90 - 97
0, 0, 0, 0, 0, 0, 0, 0, // 98 - 9F
0, 0, 0, 0, 0, 0, 0, 0, // A0 - A7
0, 0, 0, 0, 0, 0, 0, 0, // A8 - AF
0, 0, 0, 0, 0, 0, 0, 0, // B0 - B7
0, 0, 0, 0, 0, 0, 0, 0, // B8 - BF
0, 0, 0, 0, 0, 0, 0, 0, // C0 - C7
0, 0, 0, 0, 0, 0, 0, 0, // C8 - CF
0, 0, 0, 0, 0, 0, 0, 0, // D0 - D7
0, 0, 0, 0, 0, 0, 0, 0, // D8 - DF
0, 0, 0, 0, 0, 0, 0, 0, // E0 - E7
0, 0, 0, 0, 0, 0, 0, 0, // E8 - EF
0, 0, 0, 0, 0, 0, 0, 0, // F0 - F7
0, 0, 0, 0, 0, 0, 0, 0, // F8 - FF
};
private String val;
protected StringValue(final ValueType type) {
super(type);
assert type == ValueType.UTFString || type == ValueType.NullTermString;
}
@Override
int readFromBinary(final DataInput in) throws IOException {
switch (getType()) {
case UTFString: {
final int len = in.readUnsignedShort();
val = readUTF(in, len);
return len + 2;
}
case NullTermString:
int cap = 32;
int len = 0;
byte[] buf = new byte[cap];
while (true) {
final byte b = in.readByte();
if (b == 0) break;
if (cap == len) {
cap *= 2;
final byte[] buf2 = new byte[cap];
System.arraycopy(buf, 0, buf2, 0, len);
buf = buf2;
}
buf[len++] = b;
}
val = new String(buf, 0, len, "ISO-8859-1");
return len + 1;
default:
throw new RuntimeException("Invalid type for this class");
}
}
@Override
int writeToBinary(final DataOutput out) throws IOException {
switch (getType()) {
case UTFString:
return writeUTF(val, out);
case NullTermString:
final byte[] ba = val.getBytes("ISO-8859-1");
out.write(ba);
out.write((byte) 0);
return ba.length + 1;
default:
throw new RuntimeException("Invalid type for this class");
}
}
@Override
void readFromAscii(final byte[] in, final int len) {
try {
val = new String(in, 0, len, "ISO-8859-1");
} catch (final UnsupportedEncodingException e) {
throw new InternalException(e);
}
}
@Override
int writeToAscii(final DataOutput out) throws IOException {
final byte[] ba = val.getBytes("ISO-8859-1");
out.write(ba);
return ba.length;
}
@Override
public String toString() {
return String.format("'%s'", val);
}
@Override
public String asString() {
return val;
}
@Override
public byte[] asByteArray() {
try {
return val.getBytes("ISO-8859-1");
} catch (final UnsupportedEncodingException e) {
throw new InternalException(
"Character-Set ISO-8859-1 not supported!");
}
}
@Override
boolean mustWriteAsciiAsHex() {
for (int i = 0; i < val.length(); ++i)
switch (ASCII_TABLE[val.charAt(i)]) {
case 0:
// the character is invalid =>
// we must write the string in hexadecimal form
return true;
case 1:
// this is a valid character
break;
case 2:
// whitespace is disallowed only on the end of the string
if (i == 0 || i == val.length() - 1) return true;
break;
case 3:
// ':' is disallowed only when ambiguous (position 1 and 2)
if (i == 1 || i == 2) return true;
break;
}
// all characters are valid
return false;
}
@Override
public Value set(final String content) {
val = content;
return this;
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof StringValue)) return false;
return val.equals(((StringValue) o).val);
}
@Override
public int hashCode() {
return val.hashCode();
}
// copied from DataInputStream but removed reading utflen
private static String readUTF(final DataInput in, final int utflen)
throws IOException {
final byte[] bytearr = new byte[utflen];
final char[] chararr = new char[utflen];
int c, char2, char3;
int count = 0;
int charArrCount = 0;
in.readFully(bytearr, 0, utflen);
while (count < utflen) {
c = bytearr[count] & 0xff;
if (c > 127) break;
count++;
chararr[charArrCount++] = (char) c;
}
while (count < utflen) {
c = bytearr[count] & 0xff;
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
/* 0xxxxxxx */
count++;
chararr[charArrCount++] = (char) c;
break;
case 12:
case 13:
/* 110x xxxx 10xx xxxx */
count += 2;
if (count > utflen)
throw new UTFDataFormatException(
"malformed input: partial character at end");
char2 = bytearr[count - 1];
if ((char2 & 0xC0) != 0x80)
throw new UTFDataFormatException(
"malformed input around byte " + count);
chararr[charArrCount++] = (char) ((c & 0x1F) << 6 | char2 & 0x3F);
break;
case 14:
/* 1110 xxxx 10xx xxxx 10xx xxxx */
count += 3;
if (count > utflen)
throw new UTFDataFormatException(
"malformed input: partial character at end");
char2 = bytearr[count - 2];
char3 = bytearr[count - 1];
if ((char2 & 0xC0) != 0x80 || (char3 & 0xC0) != 0x80)
throw new UTFDataFormatException(
"malformed input around byte " + (count - 1));
chararr[charArrCount++] = (char) ((c & 0x0F) << 12
| (char2 & 0x3F) << 6 | (char3 & 0x3F) << 0);
break;
default:
/* 10xx xxxx, 1111 xxxx */
throw new UTFDataFormatException("malformed input around byte "
+ count);
}
}
// The number of chars produced may be less than utflen
return new String(chararr, 0, charArrCount);
}
// copied from DataOutputStream (is not public there)
private static int writeUTF(final String str, final DataOutput out)
throws IOException {
final int strlen = str.length();
int utflen = 0;
int c, count = 0;
/* use charAt instead of copying String to char array */
for (int i = 0; i < strlen; i++) {
c = str.charAt(i);
if (c >= 0x0001 && c <= 0x007F)
utflen++;
else if (c > 0x07FF)
utflen += 3;
else
utflen += 2;
}
if (utflen > 65535)
throw new UTFDataFormatException("encoded string too long: "
+ utflen + " bytes");
final byte[] bytearr = new byte[utflen + 2];
bytearr[count++] = (byte) (utflen >>> 8 & 0xFF);
bytearr[count++] = (byte) (utflen >>> 0 & 0xFF);
int i = 0;
for (i = 0; i < strlen; i++) {
c = str.charAt(i);
if (!(c >= 0x0001 && c <= 0x007F)) break;
bytearr[count++] = (byte) c;
}
for (; i < strlen; i++) {
c = str.charAt(i);
if (c >= 0x0001 && c <= 0x007F)
bytearr[count++] = (byte) c;
else if (c > 0x07FF) {
bytearr[count++] = (byte) (0xE0 | c >> 12 & 0x0F);
bytearr[count++] = (byte) (0x80 | c >> 6 & 0x3F);
bytearr[count++] = (byte) (0x80 | c >> 0 & 0x3F);
} else {
bytearr[count++] = (byte) (0xC0 | c >> 6 & 0x1F);
bytearr[count++] = (byte) (0x80 | c >> 0 & 0x3F);
}
}
out.write(bytearr, 0, utflen + 2);
return utflen + 2;
}
}