| package SQLite; |
| |
| /** |
| * String encoder/decoder for SQLite. |
| * |
| * This module was kindly donated by Eric van der Maarel of Nedap N.V. |
| * |
| * This encoder was implemented based on an original idea from an anonymous |
| * author in the source code of the SQLite distribution. |
| * I feel obliged to provide a quote from the original C-source code: |
| * |
| * "The author disclaims copyright to this source code. In place of |
| * a legal notice, here is a blessing: |
| * |
| * May you do good and not evil. |
| * May you find forgiveness for yourself and forgive others. |
| * May you share freely, never taking more than you give." |
| * |
| */ |
| |
| public class StringEncoder { |
| |
| /** |
| * Encodes the given byte array into a string that can be used by |
| * the SQLite database. The database cannot handle null (0x00) and |
| * the character '\'' (0x27). The encoding consists of escaping |
| * these characters with a reserved character (0x01). The escaping |
| * is applied after determining and applying a shift that minimizes |
| * the number of escapes required. |
| * With this encoding the data of original size n is increased to a |
| * maximum of 1+(n*257)/254. |
| * For sufficiently large n the overhead is thus less than 1.2%. |
| * @param a the byte array to be encoded. A null reference is handled as |
| * an empty array. |
| * @return the encoded bytes as a string. When an empty array is |
| * provided a string of length 1 is returned, the value of |
| * which is bogus. |
| * When decoded with this class' <code>decode</code> method |
| * a string of size 1 will return an empty byte array. |
| */ |
| |
| public static String encode(byte[] a) { |
| // check input |
| if (a == null || a.length == 0) { |
| // bogus shift, no data |
| return "x"; |
| } |
| // determine count |
| int[] cnt = new int[256]; |
| for (int i = 0 ; i < a.length; i++) { |
| cnt[a[i] & 0xff]++; |
| } |
| // determine shift for minimum number of escapes |
| int shift = 1; |
| int nEscapes = a.length; |
| for (int i = 1; i < 256; i++) { |
| if (i == '\'') { |
| continue; |
| } |
| int sum = cnt[i] + cnt[(i + 1) & 0xff] + cnt[(i + '\'') & 0xff]; |
| if (sum < nEscapes) { |
| nEscapes = sum; |
| shift = i; |
| if (nEscapes == 0) { |
| // cannot become smaller |
| break; |
| } |
| } |
| } |
| // construct encoded output |
| int outLen = a.length + nEscapes + 1; |
| StringBuffer out = new StringBuffer(outLen); |
| out.append((char)shift); |
| for (int i = 0; i < a.length; i++) { |
| // apply shift |
| char c = (char)((a[i] - shift)&0xff); |
| // insert escapes |
| if (c == 0) { // forbidden |
| out.append((char)1); |
| out.append((char)1); |
| } else if (c == 1) { // escape character |
| out.append((char)1); |
| out.append((char)2); |
| } else if (c == '\'') { // forbidden |
| out.append((char)1); |
| out.append((char)3); |
| } else { |
| out.append(c); |
| } |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * Decodes the given string that is assumed to be a valid encoding |
| * of a byte array. Typically the given string is generated by |
| * this class' <code>encode</code> method. |
| * @param s the given string encoding. |
| * @return the byte array obtained from the decoding. |
| * @throws IllegalArgumentException when the string given is not |
| * a valid encoded string for this encoder. |
| */ |
| |
| public static byte[] decode(String s) { |
| char[] a = s.toCharArray(); |
| if (a.length > 2 && a[0] == 'X' && |
| a[1] == '\'' && a[a.length-1] == '\'') { |
| // SQLite3 BLOB syntax |
| byte[] result = new byte[(a.length-3)/2]; |
| for (int i = 2, k = 0; i < a.length - 1; i += 2, k++) { |
| byte tmp; |
| switch (a[i]) { |
| case '0': tmp = 0; break; |
| case '1': tmp = 1; break; |
| case '2': tmp = 2; break; |
| case '3': tmp = 3; break; |
| case '4': tmp = 4; break; |
| case '5': tmp = 5; break; |
| case '6': tmp = 6; break; |
| case '7': tmp = 7; break; |
| case '8': tmp = 8; break; |
| case '9': tmp = 9; break; |
| case 'A': |
| case 'a': tmp = 10; break; |
| case 'B': |
| case 'b': tmp = 11; break; |
| case 'C': |
| case 'c': tmp = 12; break; |
| case 'D': |
| case 'd': tmp = 13; break; |
| case 'E': |
| case 'e': tmp = 14; break; |
| case 'F': |
| case 'f': tmp = 15; break; |
| default: tmp = 0; break; |
| } |
| result[k] = (byte) (tmp << 4); |
| switch (a[i+1]) { |
| case '0': tmp = 0; break; |
| case '1': tmp = 1; break; |
| case '2': tmp = 2; break; |
| case '3': tmp = 3; break; |
| case '4': tmp = 4; break; |
| case '5': tmp = 5; break; |
| case '6': tmp = 6; break; |
| case '7': tmp = 7; break; |
| case '8': tmp = 8; break; |
| case '9': tmp = 9; break; |
| case 'A': |
| case 'a': tmp = 10; break; |
| case 'B': |
| case 'b': tmp = 11; break; |
| case 'C': |
| case 'c': tmp = 12; break; |
| case 'D': |
| case 'd': tmp = 13; break; |
| case 'E': |
| case 'e': tmp = 14; break; |
| case 'F': |
| case 'f': tmp = 15; break; |
| default: tmp = 0; break; |
| } |
| result[k] |= tmp; |
| } |
| return result; |
| } |
| // first element is the shift |
| byte[] result = new byte[a.length-1]; |
| int i = 0; |
| int shift = s.charAt(i++); |
| int j = 0; |
| while (i < s.length()) { |
| int c; |
| if ((c = s.charAt(i++)) == 1) { // escape character found |
| if ((c = s.charAt(i++)) == 1) { |
| c = 0; |
| } else if (c == 2) { |
| c = 1; |
| } else if (c == 3) { |
| c = '\''; |
| } else { |
| throw new IllegalArgumentException( |
| "invalid string passed to decoder: " + j); |
| } |
| } |
| // do shift |
| result[j++] = (byte)((c + shift) & 0xff); |
| } |
| int outLen = j; |
| // provide array of correct length |
| if (result.length != outLen) { |
| result = byteCopy(result, 0, outLen, new byte[outLen]); |
| } |
| return result; |
| } |
| |
| /** |
| * Copies count elements from source, starting at element with |
| * index offset, to the given target. |
| * @param source the source. |
| * @param offset the offset. |
| * @param count the number of elements to be copied. |
| * @param target the target to be returned. |
| * @return the target being copied to. |
| */ |
| |
| private static byte[] byteCopy(byte[] source, int offset, |
| int count, byte[] target) { |
| for (int i = offset, j = 0; i < offset + count; i++, j++) { |
| target[j] = source[i]; |
| } |
| return target; |
| } |
| |
| |
| static final char[] xdigits = { |
| '0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' |
| }; |
| |
| /** |
| * Encodes the given byte array into SQLite3 blob notation, ie X'..' |
| * @param a the byte array to be encoded. A null reference is handled as |
| * an empty array. |
| * @return the encoded bytes as a string. |
| */ |
| |
| public static String encodeX(byte[] a) { |
| // check input |
| if (a == null || a.length == 0) { |
| return "X''"; |
| } |
| int outLen = a.length * 2 + 3; |
| StringBuffer out = new StringBuffer(outLen); |
| out.append('X'); |
| out.append('\''); |
| for (int i = 0; i < a.length; i++) { |
| out.append(xdigits[(a[i] >> 4) & 0x0F]); |
| out.append(xdigits[a[i] & 0x0F]); |
| } |
| out.append('\''); |
| return out.toString(); |
| } |
| } |