/*
Copyright 2006 by Sean Luke
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package ec.util;
import java.io.*;
import ec.*;
/*
* Code.java
*
* Created: Sat Oct 23 13:45:20 1999
* By: Sean Luke
*/
/**
* Code provides some simple wrapper functions for encoding and decoding
* basic data types for storage in a pseudo-Java source code strings
* format. This differs from just "printing"
* them to string in that the actual precision of the object is maintained.
* Code attempts to keep the representations as "Java-like" as possible --
* the exceptions being primarily floats and doubles, which are encoded as
* ints and longs. Encoding of objects and arrays is not supported. You'll
* have to handle that yourself. Strings are supported.
*
*
Everything is case-SENSITIVE. Here's the breakdown.
*
Type | Format |
boolean | true or false (old style, case sensitive) or T or F (new style, case sensitive) |
byte | bbyte| |
short | sshort| |
int | iint| |
long | llong| |
float | ffloatConvertedToIntForStorage|humanReadableFloat| or (only for reading in) f|humanReadableFloat| |
float | ddoubleConvertedToLongForStorage|humanReadableDouble| or (only for reading in) d|humanReadableDouble| |
char | standard Java char, except that the only valid escape sequences are: \0 \t \n \b \' \" \ u unicodeHex |
string | standard Java string with \ u ...\ u Unicode escapes, except that the only other valid escape sequences are: \0 \t \n \b \' \" |
*
*
* @author Sean Luke
* @version 1.0
*/
public class Code
{
/** Encodes a boolean. */
public static String encode(final boolean b)
// old style -- no longer used
// { return b ? Boolean.TRUE.toString() : Boolean.FALSE.toString(); }
{ return b ? "T" : "F"; }
/** Encodes a byte. */
public static String encode(final byte b)
{ return "b" + Byte.toString(b) + "|"; }
/** Encodes a character. */
public static String encode(final char c)
{
if (c >= 32 && c < 127 && c !='\\' &&
c!= '\'') // we can safely print it
return "'" + String.valueOf(c) + "'";
else
{
// print it with an escape character
if (c=='\b')
return "'\\b'";
else if (c=='\n')
return "'\\n'";
else if (c=='\t')
return "'\\t'";
else if (c=='\'')
return "'\\''";
else if (c=='\\')
return "'\\\\'";
else if (c=='\0')
return "'\\\\0";
else
{
String s = Integer.toHexString((int)c);
// pad with 0's -- Java's parser freaks out otherwise
switch (s.length())
{
case 1: s = "'\\u000" + s + "'"; break;
case 2: s = "'\\u00" + s + "'"; break;
case 3: s = "'\\u0" + s + "'"; break;
case 4: s = "'\\u" + s + "'"; break;
}
return s;
}
}
}
/** Encodes a short. */
public static String encode(final short s)
{ return "s" + Short.toString(s) + "|"; }
/** Encodes an int. */
public static String encode(final int i)
{ return "i" + Integer.toString(i) + "|"; }
/** Encodes a long. */
public static String encode(final long l)
{ return "l" + Long.toString(l) + "|"; }
/** Encodes a float. */
public static String encode(final float f)
{ return "f" + Integer.toString(Float.floatToIntBits(f))+ "|" + String.valueOf(f) + "|"; }
/** Encodes a double. */
public static String encode(final double d)
{ return "d" + Long.toString(Double.doubleToLongBits(d))+ "|" + String.valueOf(d) + "|"; }
/** Encodes a String. */
public static String encode(final String s)
{
boolean inUnicode = false;
int l = s.length();
StringBuffer sb = new StringBuffer(l);
sb.append("\"");
for(int x=0;x= 32 && c < 127 && c !='\\' && c!= '"') // we allow spaces
// we can safely print it
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append(c);
}
else
{
// print it with an escape character
if (c=='\b')
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append("\\b");
}
else if (c=='\n')
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append("\\n");
}
else if (c=='\t')
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append("\\t");
}
else if (c=='"')
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append("\\\"");
}
else if (c=='\\')
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append("\\\\");
}
else if (c=='\0')
{
if (inUnicode) { sb.append("\\u"); inUnicode=false; }
sb.append("\\0");
}
else
{
if (!inUnicode) {sb.append("\\u"); inUnicode=true; }
String ss = Integer.toHexString((int)c);
// pad with 0's -- Java's parser freaks out otherwise
switch (ss.length())
{
case 1: sb.append("000" + ss); break;
case 2: sb.append("00" + ss); break;
case 3: sb.append("0" + ss); break;
case 4: sb.append(ss); break;
}
}
}
}
if (inUnicode) sb.append("\\u");
sb.append("\"");
return sb.toString();
}
/** Decodes the next item out of a DecodeReturn and modifies the DecodeReturn to hold the results. See DecodeReturn for more
explanations about how to interpret the results. */
public static void decode(DecodeReturn d)
{
String dat = d.data;
int x = d.pos;
int len = d.data.length();
// look for whitespace or ( or )
for ( ; x= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a float"; return; }
if (!readHuman)
sf = dat.substring(initial,x);
x++;
// look for next '|'
int initial2 = x; // x is now just past first |
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a float"; return; }
if (readHuman)
sf = dat.substring(initial2,x);
float f;
try
{
if (readHuman) f = Float.parseFloat(sf);
else f = Float.intBitsToFloat(Integer.parseInt(sf));
}
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a float"; return; }
d.type = DecodeReturn.T_FLOAT;
d.d = f;
d.pos = x+1;
return;
}
case 'd': // double
{
boolean readHuman = false;
String sf = null;
int initial = x+1;
// look for next '|'
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if (x==initial) readHuman=true;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a double"; return; }
if (!readHuman)
sf = dat.substring(initial,x);
x++;
// look for next '|'
int initial2 = x; // x is now just past first |
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a double"; return; }
if (readHuman)
sf = dat.substring(initial2,x);
double f;
try
{
if (readHuman) f = Double.parseDouble(sf);
else f = Double.longBitsToDouble(Long.parseLong(sf));
}
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a double"; return; }
d.type = DecodeReturn.T_DOUBLE;
d.d = f;
d.pos = x+1;
return;
}
// break;
case 'b': // byte
{
int initial = x+1;
// look for next '|'
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a byte"; return; }
String sf = dat.substring(initial,x);
byte f;
try
{ f = Byte.parseByte(sf); }
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a byte"; return; }
d.type = DecodeReturn.T_BYTE;
d.l = f;
d.pos = x+1;
return;
}
// break;
case 's': // short
{
int initial = x+1;
// look for next '|'
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a short"; return; }
String sf = dat.substring(initial,x);
short f;
try
{ f = Short.parseShort(sf); }
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a short"; return; }
d.type = DecodeReturn.T_SHORT;
d.l = f;
d.pos = x+1;
return;
}
// break;
case 'i': // int
{
int initial = x+1;
// look for next '|'
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected an int"; return; }
String sf = dat.substring(initial,x);
int f;
try
{ f = Integer.parseInt(sf); }
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected an int"; return; }
d.type = DecodeReturn.T_INT;
d.l = f;
d.pos = x+1;
return;
}
// break;
case 'l': // long
{
int initial = x+1;
// look for next '|'
for ( ; x < len; x++)
if (dat.charAt(x)=='|') break;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a long"; return; }
String sf = dat.substring(initial,x);
long f;
try
{ f = Long.parseLong(sf); }
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Expected a long"; return; }
d.type = DecodeReturn.T_LONG;
d.l = f;
d.pos = x+1;
return;
}
// break;
case '"': // string
{
StringBuffer sb = new StringBuffer();
boolean inUnicode = false;
x++;
for ( ; x < len; x++)
{
char c = dat.charAt(x);
if (c=='"')
{
// done with the string
if (inUnicode) // uh oh
{ d.type = DecodeReturn.T_ERROR; d.s = "Forgot to terminate Unicode with a '\\u' in the string"; return; }
d.type = DecodeReturn.T_STRING;
d.s = sb.toString();
d.pos = x+1;
return;
}
else if (c=='\\') // escape
{
x++;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Unterminated String"; return; }
if (dat.charAt(x)!='u')
{ d.type = DecodeReturn.T_ERROR; d.s = "Escape character in Unicode sequence"; return; }
switch (dat.charAt(x))
{
case 'u': inUnicode = !inUnicode; break;
case 'b': sb.append('\b'); break;
case 'n': sb.append('\n'); break;
case '"': sb.append('"'); break;
case '\'': sb.append('\''); break;
case 't': sb.append('\t'); break;
case '\\': sb.append('\\'); break;
case '0': sb.append('\0'); break;
default:
{ d.type = DecodeReturn.T_ERROR; d.s = "Bad escape char in String"; return; }
}
}
else if (inUnicode)
{
if ( x + 3 >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Unterminated String"; return; }
try
{
sb.append((char)(Integer.decode("0x" + c +
dat.charAt(x+1) +
dat.charAt(x+2) +
dat.charAt(x+3)).intValue()));;
x+=3;
}
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Bad Unicode in String"; return; }
}
else sb.append(c);
}
d.type = DecodeReturn.T_ERROR; d.s = "Unterminated String"; return;
}
//break;
case '\'': // char
{
x++;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Unterminated char"; return; }
char c = dat.charAt(x);
if (c=='\\')
{
x++;
if (x>=len)
{ d.type = DecodeReturn.T_ERROR; d.s = "Unterminated char"; return; }
switch (dat.charAt(x))
{
case 'u':
if ( x + 4 >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Unterminated char"; return; }
try
{
c = (char)(Integer.decode("0x" +
dat.charAt(x+1) +
dat.charAt(x+2) +
dat.charAt(x+3) +
dat.charAt(x+4)).intValue());
}
catch (NumberFormatException e)
{ d.type = DecodeReturn.T_ERROR; d.s = "Bad Unicode in char"; return; }
x+=5;
break;
case 'b': c = '\b'; x++; break;
case 'n': c = '\n'; x++; break;
case '"': c = '"'; x++; break;
case '\'': c = '\''; x++; break;
case 't': c = '\t'; x++; break;
case '\\': c = '\\'; x++; break;
case '0': c = '\0'; x++; break;
default:
{ d.type = DecodeReturn.T_ERROR; d.s = "Bad escape char in char"; return; }
}
if (dat.charAt(x)!='\'')
{ d.type = DecodeReturn.T_ERROR; d.s = "Bad char"; return; }
d.type = DecodeReturn.T_CHAR;
d.l = c;
d.pos = x+1;
return;
}
else
{
x++;
if ( x >= len )
{ d.type = DecodeReturn.T_ERROR; d.s = "Unterminated char"; return; }
if (dat.charAt(x)!='\'')
{ d.type = DecodeReturn.T_ERROR; d.s = "Bad char"; return; }
d.type = DecodeReturn.T_CHAR;
d.l = c;
d.pos = x + 1;
return;
}
}
//break;
default:
d.type = DecodeReturn.T_ERROR; d.s = "Unknown token"; return;
// break;
}
}
/** Finds the next nonblank line, then trims the line and checks the preamble. Returns a DecodeReturn on the line if successful, else posts a fatal error.
Sets the DecodeReturn's line number. The DecodeReturn has not yet been decoded. You'll need to do that with Code.decode(...) */
public static DecodeReturn checkPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
int linenumber = 0; // throw it away later
try
{
// get non-blank line
String s = "";
while(s != null && s.trim().equals(""))
{
linenumber = reader.getLineNumber();
s = reader.readLine();
}
// check the preamble
if (s==null || !(s = s.trim()).startsWith(preamble)) // uh oh
state.output.fatal("Line " + linenumber +
" has a bad preamble.Expected '" + preamble + "'\n-->" + s);
DecodeReturn d = new DecodeReturn(s, preamble.length());
d.lineNumber = linenumber;
return d;
}
catch (IOException e)
{
state.output.fatal("On line " + linenumber + " an IO error occurred:\n\n" + e);
return null; // never happens
}
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a string if there is one, and returns it.
Generates an error otherwise. */
public static String readStringWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_STRING)
state.output.fatal("Line " + d.lineNumber +
" has no string after preamble '" + preamble + "'\n-->" + d.data);
return (String)(d.s);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a character if there is one, and returns it.
Generates an error otherwise. */
public static char readCharacterWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_CHAR)
state.output.fatal("Line " + d.lineNumber +
" has no character after preamble '" + preamble + "'\n-->" + d.data);
return (char)(d.l);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a byte if there is one, and returns it.
Generates an error otherwise. */
public static byte readByteWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_BYTE)
state.output.fatal("Line " + d.lineNumber +
" has no byte after preamble '" + preamble + "'\n-->" + d.data);
return (byte)(d.l);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a short if there is one, and returns it.
Generates an error otherwise. */
public static short readShortWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_SHORT)
state.output.fatal("Line " + d.lineNumber +
" has no short after preamble '" + preamble + "'\n-->" + d.data);
return (short)(d.l);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a long if there is one, and returns it.
Generates an error otherwise. */
public static long readLongWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_LONG)
state.output.fatal("Line " + d.lineNumber +
" has no long after preamble '" + preamble + "'\n-->" + d.data);
return (long)(d.l);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in an integer if there is one, and returns it.
Generates an error otherwise. */
public static int readIntegerWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_INT)
state.output.fatal("Line " + d.lineNumber +
" has no integer after preamble '" + preamble + "'\n-->" + d.data);
return (int)(d.l);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a float if there is one, and returns it.
Generates an error otherwise. */
public static float readFloatWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_FLOAT)
state.output.fatal("Line " + d.lineNumber +
" has no floating point number after preamble '" + preamble + "'\n-->" + d.data);
return (float)(d.d);
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a double if there is one, and returns it.
Generates an error otherwise. */
public static double readDoubleWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_DOUBLE)
state.output.fatal("Line " + d.lineNumber +
" has no double floating point number after preamble '" + preamble + "'. -->" + d.data);
return d.d;
}
/** Finds the next nonblank line, skips past an expected preamble, and reads in a boolean value ("true" or "false") if there is one, and returns it.
Generates an error otherwise. */
public static boolean readBooleanWithPreamble(String preamble, final EvolutionState state,
final LineNumberReader reader)
{
DecodeReturn d = checkPreamble(preamble, state, reader);
Code.decode(d);
if (d.type!=DecodeReturn.T_BOOLEAN)
state.output.fatal("Line " + d.lineNumber +
" has no boolean value ('true' or 'false') after preamble '" + preamble + "'\n-->" + d.data);
return (d.l != 0);
}
}
/*
(BeanShell testing for decoding)
s = " true false s-12| i232342|b22|f234123|3234.1| d234111231|4342.31|"
s = "\"Hello\" true false s-12| i232342|b22|f234123|3234.1| d234111231|4342.31| ' ' '\\'' '\\n' \"Hello\\u0000\\uWorld\""
c = new ec.util.Code();
r = new ec.util.DecodeReturn(s);
c.decode(r);
System.out.println(r.type);
System.out.println(r.l);
System.out.println(r.d);
System.out.println(r.s);
*/