1
2 package jsdsi.sexp;
3
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOException;
6 import java.io.OutputStream;
7 import java.io.OutputStreamWriter;
8 import java.io.Serializable;
9 import java.io.StringWriter;
10 import java.io.UnsupportedEncodingException;
11 import java.io.Writer;
12
13 /***
14 *
15 * Abstract S-expression superclass. Provides static utility functions.
16 * S-expressions are immutable.
17 *
18 * @see SexpString
19 * @see SexpList
20 *
21 * @author Sameer Ajmani
22 * @version $Revision: 1.2.2.1 $ $Date: 2005/11/08 03:12:52 $
23 */
24 public abstract class Sexp implements Serializable {
25
26 private static final long serialVersionUID = 1708696962807474053L;
27
28 /***
29 * Returns the character encoding for S-expressions (8859_1).
30 *
31 * @return "8859_1"
32 **/
33 public static final String getEncoding() {
34 return "8859_1";
35 }
36 /***
37 * Tests if a character is whitespace.
38 *
39 * @param c
40 * the character to be tested
41 * @return <code>true</code> if the character is whitespace, <code>false</code>
42 * otherwise
43 */
44 public static final boolean isWhiteSpace(int c) {
45 return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
46 }
47
48 /***
49 * Tests if a character is a decimal digit.
50 *
51 * @param c
52 * the character to be tested
53 * @return <code>true</code> if the character is a digit between 0 and 9
54 * inclusive, <code>false</code> otherwise
55 */
56 public static final boolean isDecimalDigit(int c) {
57 return (c >= '0' && c <= '9');
58 }
59
60 /***
61 * Tests if a character is a hexadecimal digit.
62 *
63 * @param c
64 * the character to be tested.
65 * @return <code>true</code> if the character is a decimal digit or a
66 * letter between 'a' and 'f' upper or lowercase, <code>false</code>
67 * otherwise .
68 */
69 public static final boolean isHexDigit(int c) {
70 return (
71 isDecimalDigit(c)
72 || (c >= 'a' && c <= 'f')
73 || (c >= 'A' && c <= 'F'));
74 }
75
76 /***
77 * Tests if a character is a base 64 digit.
78 *
79 * @param c
80 * the character to be tested.
81 * @return <code>true</code> if the character is a decimal digit, a
82 * letter of the alphabet or '+' or '/', <code>false</code>
83 * otherwise.
84 */
85 public static final boolean isBase64Digit(int c) {
86 return (
87 isDecimalDigit(c)
88 || (c >= 'a' && c <= 'z')
89 || (c >= 'A' && c <= 'Z')
90 || c == '+'
91 || c == '/');
92 }
93
94 /***
95 * Tests if a character is a token character.
96 *
97 * @param c
98 * the character to be tested.
99 * @return <code>true</code> if the character is a legal token character
100 * for an S-expression, <code>false</code> otherwise.
101 */
102 public static final boolean isTokenChar(int c) {
103 return (
104 isBase64Digit(c)
105 || c == '.'
106 || c == '-'
107 || c == '='
108 || c == '*'
109 || c == ':'
110 || c == '_');
111 }
112
113 /***
114 * Maps integers to hexadecimal digits.
115 */
116 public static final char[] hexDigit =
117 {
118 '0',
119 '1',
120 '2',
121 '3',
122 '4',
123 '5',
124 '6',
125 '7',
126 '8',
127 '9',
128 'A',
129 'B',
130 'C',
131 'D',
132 'E',
133 'F' };
134
135 /***
136 * Encodes data in hexadecimal and writes it to a character stream
137 * without any line-wrapping.
138 *
139 * @param data
140 * the data to encode.
141 * @param out
142 * the character stream.
143 */
144 static void writeHex(byte[] data, Writer out) throws IOException {
145 for (int i = 0; i < data.length; i++) {
146 out.write(Sexp.hexDigit[data[i] >>> 4 & 0x0F]);
147 out.write(Sexp.hexDigit[data[i] & 0x0F]);
148 }
149 }
150
151 /***
152 * Encodes data in hexadecimal and writes it to a character stream
153 * with appropriat line-wrapping.
154 *
155 * @param data
156 * the data to encode.
157 * @param out
158 * the character stream.
159 * @param offset
160 * spaces indented from left.
161 * @param width
162 * total width of window, in characters.
163 * @param last
164 * spaces reserved on right (e.g., for closing parens).
165 */
166 static void writeHex(
167 byte[] data,
168 Writer out,
169 int offset,
170 int width,
171 int last)
172 throws IOException {
173 StringWriter w = new StringWriter();
174 writeHex(data, w);
175 writeWrapped(w.toString(), out, offset, width, last);
176 }
177
178 /***
179 * Maps integers to base-64 digits.
180 */
181 public static final char[] base64Digit =
182 {
183 'A',
184 'B',
185 'C',
186 'D',
187 'E',
188 'F',
189 'G',
190 'H',
191 'I',
192 'J',
193 'K',
194 'L',
195 'M',
196 'N',
197 'O',
198 'P',
199 'Q',
200 'R',
201 'S',
202 'T',
203 'U',
204 'V',
205 'W',
206 'X',
207 'Y',
208 'Z',
209 'a',
210 'b',
211 'c',
212 'd',
213 'e',
214 'f',
215 'g',
216 'h',
217 'i',
218 'j',
219 'k',
220 'l',
221 'm',
222 'n',
223 'o',
224 'p',
225 'q',
226 'r',
227 's',
228 't',
229 'u',
230 'v',
231 'w',
232 'x',
233 'y',
234 'z',
235 '0',
236 '1',
237 '2',
238 '3',
239 '4',
240 '5',
241 '6',
242 '7',
243 '8',
244 '9',
245 '+',
246 '/' };
247
248 /***
249 * Encodes data in base-64 and writes it to a character stream without any
250 * line-wrapping.
251 *
252 * @param data
253 * the data to encode.
254 * @param out
255 * the character stream.
256 */
257 static void writeBase64(byte[] data, Writer out) throws IOException {
258 int carry = 0;
259 for (int i = 0; i < data.length; i++) {
260 switch (i % 3) {
261 case 0 :
262 out.write(Sexp.base64Digit[data[i] >>> 2 & 0x3F]);
263 carry = data[i] << 4 & 0x30;
264 break;
265 case 1 :
266 out.write(Sexp.base64Digit[carry + (data[i] >>> 4 & 0x0F)]);
267 carry = data[i] << 2 & 0x3C;
268 break;
269 case 2 :
270 out.write(Sexp.base64Digit[carry + (data[i] >>> 6 & 0x03)]);
271 out.write(Sexp.base64Digit[data[i] & 0x3F]);
272 carry = 0;
273 break;
274 }
275 }
276 switch (data.length % 3) {
277 case 1 :
278 out.write(Sexp.base64Digit[carry & 0x3F]);
279 out.write('=');
280 out.write('=');
281 break;
282 case 2 :
283 out.write(Sexp.base64Digit[carry & 0x3F]);
284 out.write('=');
285 break;
286 default :
287 break;
288 }
289 }
290
291 /***
292 * Encodes data in base-64 and writes it to a character stream with
293 * appropriate line-wrapping.
294 *
295 * @param data
296 * the data to encode.
297 * @param out
298 * the character stream.
299 * @param offset
300 * spaces indented from left.
301 * @param width
302 * total width of window, in characters.
303 * @param last
304 * spaces reserved on right (e.g., for closing parens).
305 */
306 static void writeBase64(
307 byte[] data,
308 Writer out,
309 int offset,
310 int width,
311 int last)
312 throws IOException {
313 StringWriter w = new StringWriter();
314 writeBase64(data, w);
315 writeWrapped(w.toString(), out, offset, width, last);
316 }
317
318 static void newline(Writer out, int offset) throws IOException {
319 out.write('\n');
320 for (int i = 0; i < offset; i++) {
321 out.write(' ');
322 }
323 }
324
325 /***
326 * Writes s to out with line-wrapping as needed to fit in the window.
327 *
328 * @param s
329 * the string to write.
330 * @param out
331 * the character stream.
332 * @param offset
333 * spaces indented from left.
334 * @param width
335 * total width of window, in characters.
336 * @param last
337 * spaces reserved on right (e.g., for closing parens).
338 * @throws IOException
339 */
340 static void writeWrapped(
341 String s,
342 Writer out,
343 int offset,
344 int width,
345 int last)
346 throws IOException {
347 char[] cs = s.toCharArray();
348 int linesize = Math.max(width - offset - last, 2);
349 int i = 0;
350 while (true) {
351 int towrite = Math.min(cs.length - i, linesize);
352 out.write(cs, i, towrite);
353 i += towrite;
354 if (i < cs.length) {
355 newline(out, offset);
356 } else {
357 break;
358 }
359 }
360 }
361
362 /***
363 * Converts a byte array to a string using the 8859_1 encoding.
364 */
365 public static String decodeString(byte[] b) {
366 try {
367 return new String(b, getEncoding());
368 } catch (UnsupportedEncodingException e) {
369 throw new Error(e);
370 }
371 }
372
373 /***
374 * Converts a string to a byte array using the 8859_1 encoding.
375 */
376 public static byte[] encodeString(String s) {
377 try {
378 return s.getBytes(getEncoding());
379 } catch (UnsupportedEncodingException e) {
380 throw new Error(e);
381 }
382 }
383
384 /***
385 * Writes this S-expression to a byte stream in canonical form.
386 */
387 public abstract void writeCanonical(OutputStream out) throws IOException;
388
389 /***
390 * Writes this S-expression to a byte stream in transport form.
391 */
392 public final void writeTransport(OutputStream out) throws IOException {
393 OutputStreamWriter w = new OutputStreamWriter(out);
394 w.write('{');
395 // NOTE: Cryptix's Base64OutputStream doesn't work here
396 ByteArrayOutputStream bos = new ByteArrayOutputStream();
397 writeCanonical(bos);
398 byte[] data = bos.toByteArray();
399 writeBase64(data, w, 0, 4 * ((data.length + 2) / 3), 0);
400 w.write('}');
401 w.flush();
402 }
403
404 /***
405 * Writes this S-expression to a character stream in readable form.
406 *
407 * @param offset
408 * spaces indented from left.
409 * @param width
410 * total width of window, in characters.
411 * @param last
412 * spaces reserved on right (e.g., for closing parens).
413 */
414 public abstract void writeReadable(
415 Writer out,
416 int offset,
417 int width,
418 int last)
419 throws IOException;
420
421 /***
422 * Returns the length of this S-expression as if it were printed on one
423 * line. Caches result for efficiency.
424 *
425 * @see #getReadableLenImpl()
426 */
427 public final int getReadableLen() {
428 if (readableLen < 0) {
429 readableLen = getReadableLenImpl();
430 }
431 return readableLen;
432 }
433
434 private int readableLen = -1;
435
436 /***
437 * Returns the length of this S-expression as if it were printed on one
438 * line.
439 */
440 abstract int getReadableLenImpl();
441 }
This page was automatically generated by Maven