* </tr>
* </table>
*
**/
public static RubyArray unpack(Ruby runtime, ByteList encodedString, ByteList formatString) {
RubyArray result = runtime.newArray();
// FIXME: potentially could just use ByteList here?
ByteBuffer format = ByteBuffer.wrap(formatString.unsafeBytes(), formatString.begin(), formatString.length());
ByteBuffer encode = ByteBuffer.wrap(encodedString.unsafeBytes(), encodedString.begin(), encodedString.length());
int type = 0;
int next = safeGet(format);
while (next != 0) {
type = next;
next = safeGet(format);
// Next indicates to decode using native encoding format
if (next == '_' || next == '!') {
if (NATIVE_CODES.indexOf(type) == -1) {
throw runtime.newArgumentError("'" + next + "' allowed only after types " + NATIVE_CODES);
}
next = safeGet(format);
}
// How many occurrences of 'type' we want
int occurrences = 0;
if (next == 0) {
occurrences = 1;
} else {
if (next == '*') {
occurrences = IS_STAR;
next = safeGet(format);
} else if (ASCII.isDigit(next)) {
occurrences = 0;
do {
occurrences = occurrences * 10 + Character.digit((char)(next & 0xFF), 10);
next = safeGet(format);
} while (next != 0 && ASCII.isDigit(next));
} else {
occurrences = type == '@' ? 0 : 1;
}
}
// See if we have a converter for the job...
Converter converter = converters[type];
if (converter != null) {
decode(runtime, encode, occurrences, result, converter);
type = next;
continue;
}
// Otherwise the unpack should be here...
switch (type) {
case '@' :
try {
if (occurrences == IS_STAR) {
encode.position(encode.remaining());
} else {
encode.position(occurrences);
}
} catch (IllegalArgumentException iae) {
throw runtime.newArgumentError("@ outside of string");
}
break;
case '%' :
throw runtime.newArgumentError("% is not supported");
case 'A' :
{
if (occurrences == IS_STAR || occurrences > encode.remaining()) {
occurrences = encode.remaining();
}
byte[] potential = new byte[occurrences];
encode.get(potential);
for (int t = occurrences - 1; occurrences > 0; occurrences--, t--) {
byte c = potential[t];
if (c != '\0' && c != ' ') {
break;
}
}
result.append(RubyString.newString(runtime, new ByteList(potential, 0, occurrences,false)));
}
break;
case 'Z' :
{
boolean isStar = (occurrences == IS_STAR);
if (occurrences == IS_STAR || occurrences > encode.remaining()) {
occurrences = encode.remaining();
}
byte[] potential = new byte[occurrences];
int t = 0;
while (t < occurrences) {
byte b = encode.get();
if (b == 0) {
break;
}
potential[t] = b;
t++;
}
result.append(RubyString.newString(runtime, new ByteList(potential, 0, t, false)));
// In case when the number of occurences is
// explicitly specified, we have to read up
// the remaining garbage after the '\0' to
// satisfy the requested pattern.
if (!isStar) {
if (t < occurrences) {
// We encountered '\0' when
// were reading the buffer above,
// increment the number of read bytes.
t++;
}
while (t < occurrences) {
encode.get();
t++;
}
}
}
break;
case 'a' :
if (occurrences == IS_STAR || occurrences > encode.remaining()) {
occurrences = encode.remaining();
}
byte[] potential = new byte[occurrences];
encode.get(potential);
result.append(RubyString.newString(runtime, new ByteList(potential, false)));
break;
case 'b' :
{
if (occurrences == IS_STAR || occurrences > encode.remaining() * 8) {
occurrences = encode.remaining() * 8;
}
int bits = 0;
byte[] lElem = new byte[occurrences];
for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
if ((lCurByte & 7) != 0) {
bits >>>= 1;
} else {
bits = encode.get();
}
lElem[lCurByte] = (bits & 1) != 0 ? (byte)'1' : (byte)'0';
}
result.append(RubyString.newString(runtime, new ByteList(lElem, false)));
}
break;
case 'B' :
{
if (occurrences == IS_STAR || occurrences > encode.remaining() * 8) {
occurrences = encode.remaining() * 8;
}
int bits = 0;
byte[] lElem = new byte[occurrences];
for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
if ((lCurByte & 7) != 0)
bits <<= 1;
else
bits = encode.get();
lElem[lCurByte] = (bits & 128) != 0 ? (byte)'1' : (byte)'0';
}
result.append(RubyString.newString(runtime, new ByteList(lElem, false)));
}
break;
case 'h' :
{
if (occurrences == IS_STAR || occurrences > encode.remaining() * 2) {
occurrences = encode.remaining() * 2;
}
int bits = 0;
byte[] lElem = new byte[occurrences];
for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
if ((lCurByte & 1) != 0) {
bits >>>= 4;
} else {
bits = encode.get();
}
lElem[lCurByte] = sHexDigits[bits & 15];
}
result.append(RubyString.newString(runtime, new ByteList(lElem, false)));
}
break;
case 'H' :
{
if (occurrences == IS_STAR || occurrences > encode.remaining() * 2) {
occurrences = encode.remaining() * 2;
}
int bits = 0;
byte[] lElem = new byte[occurrences];
for (int lCurByte = 0; lCurByte < occurrences; lCurByte++) {
if ((lCurByte & 1) != 0)
bits <<= 4;
else
bits = encode.get();
lElem[lCurByte] = sHexDigits[(bits >>> 4) & 15];
}
result.append(RubyString.newString(runtime, new ByteList(lElem, false)));
}
break;
case 'u':
{
int length = encode.remaining() * 3 / 4;
byte[] lElem = new byte[length];
int index = 0;
int s;
int total = 0;
s = encode.get();
while (encode.hasRemaining() && s > ' ' && s < 'a') {
int a, b, c, d;
byte[] hunk = new byte[3];
int len = (s - ' ') & 077;
s = safeGet(encode);
total += len;
if (total > length) {
len -= total - length;
total = length;
}
while (len > 0) {
int mlen = len > 3 ? 3 : len;
if (encode.hasRemaining() && s >= ' ') {
a = (s - ' ') & 077;
s = safeGet(encode);
} else
a = 0;
if (encode.hasRemaining() && s >= ' ') {
b = (s - ' ') & 077;
s = safeGet(encode);
} else
b = 0;
if (encode.hasRemaining() && s >= ' ') {
c = (s - ' ') & 077;
s = safeGet(encode);
} else
c = 0;
if (encode.hasRemaining() && s >= ' ') {
d = (s - ' ') & 077;
s = safeGet(encode);
} else
d = 0;
hunk[0] = (byte)((a << 2 | b >> 4) & 255);
hunk[1] = (byte)((b << 4 | c >> 2) & 255);
hunk[2] = (byte)((c << 6 | d) & 255);
for (int i = 0; i < mlen; i++) lElem[index++] = hunk[i];
len -= mlen;
}
if (s == '\r')
s = safeGet(encode);
if (s == '\n')
s = safeGet(encode);
else if (encode.hasRemaining()) {
if (safeGet(encode) == '\n') {
safeGet(encode); // Possible Checksum Byte
} else if (encode.hasRemaining()) {
encode.position(encode.position() - 1);
}
}
}
result.append(RubyString.newString(runtime, new ByteList(lElem, 0, index, false)));
}
break;
case 'm':
{
int length = encode.remaining()*3/4;
byte[] lElem = new byte[length];
int a = -1, b = -1, c = 0, d;
int index = 0;
int s = -1;
while (encode.hasRemaining()) {
a = b = c = d = -1;
// obtain a
s = safeGet(encode);
while (((a = b64_xtable[s]) == -1) && encode.hasRemaining()) {
s = safeGet(encode);
}
if (a == -1) break;
// obtain b
s = safeGet(encode);
while (((b = b64_xtable[s]) == -1) && encode.hasRemaining()) {
s = safeGet(encode);
}
if (b == -1) break;
// obtain c
s = safeGet(encode);
while (((c = b64_xtable[s]) == -1) && encode.hasRemaining()) {
if (s == '=') break;
s = safeGet(encode);
}
if ((s == '=') || c == -1) {
if (s == '=') {
encode.position(encode.position() - 1);
}
break;
}
// obtain d
s = safeGet(encode);
while (((d = b64_xtable[s]) == -1) && encode.hasRemaining()) {
if (s == '=') break;
s = safeGet(encode);
}
if ((s == '=') || d == -1) {
if (s == '=') {
encode.position(encode.position() - 1);
}
break;
}
// calculate based on a, b, c and d
lElem[index++] = (byte)((a << 2 | b >> 4) & 255);
lElem[index++] = (byte)((b << 4 | c >> 2) & 255);
lElem[index++] = (byte)((c << 6 | d) & 255);
}
if (a != -1 && b != -1) {
if (c == -1 && s == '=') {
lElem[index++] = (byte)((a << 2 | b >> 4) & 255);
} else if(c != -1 && s == '=') {
lElem[index++] = (byte)((a << 2 | b >> 4) & 255);
lElem[index++] = (byte)((b << 4 | c >> 2) & 255);
}
}
result.append(RubyString.newString(runtime, new ByteList(lElem, 0, index, false)));
}
break;
case 'M' :
{
byte[] lElem = new byte[Math.max(encode.remaining(),0)];
int index = 0;
for(;;) {
if (!encode.hasRemaining()) break;
byte c = safeGet(encode);
if (c != '=') {
lElem[index++] = c;
} else {
if (!encode.hasRemaining()) break;
encode.mark();
byte c1 = safeGet(encode);
if (c1 == '\n') continue;
int d1 = Character.digit((char)(c1 & 0xFF), 16);
if (d1 == -1) {
encode.reset();
break;
}
encode.mark();
if (!encode.hasRemaining()) break;
byte c2 = safeGet(encode);
int d2 = Character.digit((char)(c2 & 0xFF), 16);
if (d2 == -1) {
encode.reset();
break;
}
byte value = (byte)(d1 << 4 | d2);
lElem[index++] = value;
}
}
result.append(RubyString.newString(runtime, new ByteList(lElem, 0, index, false)));
}
break;
case 'U' :
{
if (occurrences == IS_STAR || occurrences > encode.remaining()) {
occurrences = encode.remaining();
}
while (occurrences-- > 0 && encode.remaining() > 0) {
try {
// TODO: for now, we use a faithful
// reimplementation of MRI's algorithm,
// but should use UTF8Encoding facilities
// from Joni, once it starts prefroming
// UTF-8 content validation.
result.append(
runtime.newFixnum(utf8Decode(encode)));
} catch (IllegalArgumentException e) {
throw runtime.newArgumentError(e.getMessage());
}
}