/* 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. *

TypeFormat
booleantrue or false (old style, case sensitive) or T or F (new style, case sensitive)
bytebbyte|
shortsshort|
intiint|
longllong|
floatffloatConvertedToIntForStorage|humanReadableFloat| or (only for reading in) f|humanReadableFloat|
floatddoubleConvertedToLongForStorage|humanReadableDouble| or (only for reading in) d|humanReadableDouble|
charstandard Java char, except that the only valid escape sequences are: \0 \t \n \b \' \" \ u unicodeHex
stringstandard 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); */