View Javadoc
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