1package SQLite;
2
3/**
4 * String encoder/decoder for SQLite.
5 *
6 * This module was kindly donated by Eric van der Maarel of Nedap N.V.
7 *
8 * This encoder was implemented based on an original idea from an anonymous
9 * author in the source code of the SQLite distribution.
10 * I feel obliged to provide a quote from the original C-source code:
11 *
12 * "The author disclaims copyright to this source code.  In place of
13 *  a legal notice, here is a blessing:
14 *
15 *     May you do good and not evil.
16 *     May you find forgiveness for yourself and forgive others.
17 *     May you share freely, never taking more than you give."
18 *
19 */
20
21public class StringEncoder {
22
23    /**
24     * Encodes the given byte array into a string that can be used by
25     * the SQLite database. The database cannot handle null (0x00) and
26     * the character '\'' (0x27). The encoding consists of escaping
27     * these characters with a reserved character (0x01). The escaping
28     * is applied after determining and applying a shift that minimizes
29     * the number of escapes required.
30     * With this encoding the data of original size n is increased to a
31     * maximum of 1+(n*257)/254.
32     * For sufficiently large n the overhead is thus less than 1.2%.
33     * @param a the byte array to be encoded. A null reference is handled as
34     *     an empty array.
35     * @return the encoded bytes as a string. When an empty array is
36     *     provided a string of length 1 is returned, the value of
37     *     which is bogus.
38     *     When decoded with this class' <code>decode</code> method
39     *     a string of size 1 will return an empty byte array.
40     */
41
42    public static String encode(byte[] a) {
43	// check input
44	if (a == null || a.length == 0) {
45	    // bogus shift, no data
46	    return "x";
47	}
48	// determine count
49	int[] cnt = new int[256];
50	for (int i = 0 ; i < a.length; i++) {
51	    cnt[a[i] & 0xff]++;
52	}
53	// determine shift for minimum number of escapes
54	int shift = 1;
55	int nEscapes = a.length;
56	for (int i = 1; i < 256; i++) {
57	    if (i == '\'') {
58		continue;
59	    }
60	    int sum = cnt[i] + cnt[(i + 1) & 0xff] + cnt[(i + '\'') & 0xff];
61	    if (sum < nEscapes) {
62		nEscapes = sum;
63		shift = i;
64		if (nEscapes == 0) {
65		    // cannot become smaller
66		    break;
67		}
68	    }
69	}
70	// construct encoded output
71	int outLen = a.length + nEscapes + 1;
72	StringBuffer out = new StringBuffer(outLen);
73	out.append((char)shift);
74	for (int i = 0; i < a.length; i++) {
75	    // apply shift
76	    char c = (char)((a[i] - shift)&0xff);
77	    // insert escapes
78	    if (c == 0) { // forbidden
79		out.append((char)1);
80		out.append((char)1);
81	    } else if (c == 1) { // escape character
82		out.append((char)1);
83		out.append((char)2);
84	    } else if (c == '\'') { // forbidden
85		out.append((char)1);
86		out.append((char)3);
87	    } else {
88		out.append(c);
89	    }
90	}
91	return out.toString();
92    }
93
94    /**
95     * Decodes the given string that is assumed to be a valid encoding
96     * of a byte array. Typically the given string is generated by
97     * this class' <code>encode</code> method.
98     * @param s the given string encoding.
99     * @return the byte array obtained from the decoding.
100     * @throws IllegalArgumentException when the string given is not
101     *    a valid encoded string for this encoder.
102     */
103
104    public static byte[] decode(String s) {
105	char[] a = s.toCharArray();
106	if (a.length > 2 && a[0] == 'X' &&
107	    a[1] == '\'' && a[a.length-1] == '\'') {
108	    // SQLite3 BLOB syntax
109	    byte[] result = new byte[(a.length-3)/2];
110	    for (int i = 2, k = 0; i < a.length - 1; i += 2, k++) {
111		byte tmp;
112		switch (a[i]) {
113		case '0': tmp = 0; break;
114		case '1': tmp = 1; break;
115		case '2': tmp = 2; break;
116		case '3': tmp = 3; break;
117		case '4': tmp = 4; break;
118		case '5': tmp = 5; break;
119		case '6': tmp = 6; break;
120		case '7': tmp = 7; break;
121		case '8': tmp = 8; break;
122		case '9': tmp = 9; break;
123		case 'A':
124		case 'a': tmp = 10; break;
125		case 'B':
126		case 'b': tmp = 11; break;
127		case 'C':
128		case 'c': tmp = 12; break;
129		case 'D':
130		case 'd': tmp = 13; break;
131		case 'E':
132		case 'e': tmp = 14; break;
133		case 'F':
134		case 'f': tmp = 15; break;
135		default:  tmp = 0; break;
136		}
137		result[k] = (byte) (tmp << 4);
138		switch (a[i+1]) {
139		case '0': tmp = 0; break;
140		case '1': tmp = 1; break;
141		case '2': tmp = 2; break;
142		case '3': tmp = 3; break;
143		case '4': tmp = 4; break;
144		case '5': tmp = 5; break;
145		case '6': tmp = 6; break;
146		case '7': tmp = 7; break;
147		case '8': tmp = 8; break;
148		case '9': tmp = 9; break;
149		case 'A':
150		case 'a': tmp = 10; break;
151		case 'B':
152		case 'b': tmp = 11; break;
153		case 'C':
154		case 'c': tmp = 12; break;
155		case 'D':
156		case 'd': tmp = 13; break;
157		case 'E':
158		case 'e': tmp = 14; break;
159		case 'F':
160		case 'f': tmp = 15; break;
161		default:  tmp = 0; break;
162		}
163		result[k] |= tmp;
164	    }
165	    return result;
166	}
167	// first element is the shift
168	byte[] result = new byte[a.length-1];
169	int i = 0;
170	int shift = s.charAt(i++);
171	int j = 0;
172	while (i < s.length()) {
173	    int c;
174	    if ((c = s.charAt(i++)) == 1) { // escape character found
175		if ((c = s.charAt(i++)) == 1) {
176		    c = 0;
177		} else if (c == 2) {
178		    c = 1;
179		} else if (c == 3) {
180		    c = '\'';
181		} else {
182		    throw new IllegalArgumentException(
183			"invalid string passed to decoder: " + j);
184		}
185	    }
186	    // do shift
187	    result[j++] = (byte)((c + shift) & 0xff);
188	}
189	int outLen = j;
190	// provide array of correct length
191	if (result.length != outLen) {
192	    result = byteCopy(result, 0, outLen, new byte[outLen]);
193	}
194	return result;
195    }
196
197    /**
198     * Copies count elements from source, starting at element with
199     * index offset, to the given target.
200     * @param source the source.
201     * @param offset the offset.
202     * @param count the number of elements to be copied.
203     * @param target the target to be returned.
204     * @return the target being copied to.
205     */
206
207    private static byte[] byteCopy(byte[] source, int offset,
208				   int count, byte[] target) {
209	for (int i = offset, j = 0; i < offset + count; i++, j++) {
210	    target[j] = source[i];
211	}
212	return target;
213    }
214
215
216    static final char[] xdigits = {
217	'0', '1', '2', '3', '4', '5', '6', '7',
218	'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
219    };
220
221    /**
222     * Encodes the given byte array into SQLite3 blob notation, ie X'..'
223     * @param a the byte array to be encoded. A null reference is handled as
224     *     an empty array.
225     * @return the encoded bytes as a string.
226     */
227
228    public static String encodeX(byte[] a) {
229	// check input
230	if (a == null || a.length == 0) {
231	    return "X''";
232	}
233	int outLen = a.length * 2 + 3;
234	StringBuffer out = new StringBuffer(outLen);
235	out.append('X');
236	out.append('\'');
237	for (int i = 0; i < a.length; i++) {
238	    out.append(xdigits[(a[i] >> 4) & 0x0F]);
239	    out.append(xdigits[a[i] & 0x0F]);
240	}
241	out.append('\'');
242	return out.toString();
243    }
244}
245