View Javadoc
1 /* 2 * Copyright (c) 2003 3 * Information Desire GmbH 4 * All rights reserved. 5 */ 6 package com.infodesire.infobit.external.impl; 7 8 import com.infodesire.infobit.InfobitManager; 9 import com.infodesire.infobit.InfobitException; 10 import com.infodesire.infobit.InfobitSecurityException; 11 12 import com.infodesire.infobit.data.BinaryContent; 13 import com.infodesire.infobit.data.Content; 14 import com.infodesire.infobit.data.Infobit; 15 import com.infodesire.infobit.data.Script; 16 import com.infodesire.infobit.data.Template; 17 import com.infodesire.infobit.data.TextContent; 18 import com.infodesire.infobit.data.Version; 19 20 import com.infodesire.infobit.external.InfobitExporter; 21 22 import org.apache.commons.logging.Log; 23 import org.apache.commons.logging.LogFactory; 24 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.InputStreamReader; 28 import java.io.PrintWriter; 29 import java.io.Writer; 30 31 import java.util.ArrayList; 32 import java.util.Calendar; 33 import java.util.Collection; 34 import java.util.Date; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Map; 38 39 /*** 40 * Provides a straight-forward implementation to convert infobits to XML (as 41 * defined by {@link InfobitExporter}. 42 * 43 * @author peter2 44 * @created August 27, 2003 45 * @version $Revision: 1.3 $ 46 */ 47 public class SimpleExporter implements InfobitExporter { 48 49 private final static Log _log = LogFactory.getLog(SimpleExporter.class); 50 51 /*** 52 * The system ID of the DTD 53 */ 54 private final static String DTD_SYSTEM_ID = 55 "http://www.infodesire.com" + 56 "/com/infodesire/infobit/external/infobit-1.0.dtd"; 57 58 /*** 59 * Formatting state: no tag open. The cursor is positioned at the beginning 60 * of the line right after the last character written. 61 */ 62 private final static int FS_START = 0; 63 64 /*** 65 * Formatting state: start/empty tag opened, no attributes defined. Cursor 66 * is positioned right after the last character writted. 67 */ 68 private final static int FS_OPEN_INIT = 1; 69 70 /*** 71 * Formatting state: start/empty tag opened, attribtes defined. Cursor is 72 * positioned right after the last character written. 73 */ 74 private final static int FS_OPEN_CONT = 2; 75 76 /*** 77 * Width of an element indentation step 78 */ 79 private final static int TAB_WIDTH = 2; 80 81 /*** 82 * The collaborating manager 83 */ 84 private static InfobitManager _manager; 85 86 /*** 87 * The export destination 88 */ 89 private PrintWriter _printer; 90 91 /*** 92 * Stack of names of opened elements. List of <code>String</code>. 93 */ 94 private List _elementStack = new ArrayList(); 95 96 /*** 97 * Current processing state 98 */ 99 private int _formatState = FS_START; 100 101 102 /*** 103 * Gets the Manager attribute of the VelocityRenderer object 104 * 105 * @return The Manager value 106 */ 107 public InfobitManager getManager() { 108 return _manager; 109 } 110 111 112 /*** 113 * Sets the Manager attribute of the VelocityRenderer object 114 * 115 * @param manager The new Manager value 116 */ 117 public void setManager(InfobitManager manager) { 118 _manager = manager; 119 } 120 121 122 /*** 123 * Not used. 124 */ 125 public void init() { 126 } 127 128 129 /*** 130 * Converts the specified set of infobits into an XML representation. 131 * 132 * @param infobits The infobits to externalize. Infobits are 133 * externalized in the order obtained from than argument. Collection of 134 * {@link Infobit}. 135 * @param writer The destination where to write the XML 136 * representation of <code>infobits</code> to 137 * @exception InfobitException Description of Exception 138 */ 139 public synchronized void export(Collection infobits, Writer writer) 140 throws InfobitException { 141 142 try { 143 _printer = new PrintWriter(writer); 144 _printer.println("<?xml version=\"1.0\" ?>"); 145 _printer.println("<!DOCTYPE infobit-collection" + 146 " SYSTEM \"" + DTD_SYSTEM_ID + "\">"); 147 openStartTag("infobit-collection"); 148 addAttribute("format-version", "1.0"); 149 closeStartTag(); 150 151 for (Iterator i = infobits.iterator(); i.hasNext(); ) { 152 Infobit ib = (Infobit) i.next(); 153 format(ib); 154 } 155 156 endTag(); 157 // infobit-collection 158 } catch (Exception e) { 159 _log.error("error formatting set of infobits", e); 160 throw new InfobitException(e); 161 } 162 } 163 164 165 /*** 166 * Looks up a character from the base64 alphabet. The base64 alphabet is 167 * defined by e.g. RFC 3548. 168 * 169 * @param pos Position of the character to look up. Constrained to the 170 * range 0 .. 64 171 * @return Description of the Returned Value 172 */ 173 private char toBase64(int pos) { 174 int b; 175 176 if (pos < 0) { 177 throw new IllegalArgumentException("pos: " + pos); 178 } 179 else if (pos < 26) { 180 b = 'A' + pos; 181 } 182 else if (pos < 52) { 183 b = 'a' + pos - 26; 184 } 185 else if (pos < 62) { 186 b = '0' + pos - 52; 187 } 188 else if (pos == 62) { 189 b = '+'; 190 } 191 else if (pos == 63) { 192 b = '/'; 193 } 194 else { 195 throw new IllegalArgumentException("pos: " + pos); 196 } 197 198 return (char) b; 199 } 200 201 202 /*** 203 * Subjects data to base 64 encoding and writes the result to the XML 204 * document. 205 * 206 * @param data Source of the data to encode 207 * @exception IOException Description of Exception 208 */ 209 private void formatBase64(InputStream data) throws IOException { 210 // PRE: _formatState = FS_START 211 212 byte[] binary = new byte[24 / 8]; 213 char[] ascii = new char[24 / 6]; 214 int l = 0; 215 // number of ascii chunks in current line 216 217 int n = 0; 218 while ((n = fill(data, binary)) > -1) { 219 int i = 0; 220 // Next byte in ascii chunk to fill 221 222 for (; 6 * i < 8 * n; ++i) { 223 int j = 6 * i / 8; 224 int k = 6 * i % 8; 225 int low = binary[j]; 226 int high = j + 1 < n ? binary[j + 1] : 0; 227 int c = (low >>> k | high << (8 - k)) & 0x3f; 228 ascii[i] = toBase64(c); 229 } 230 231 for (; i < ascii.length; ++i) { 232 ascii[i] = '='; 233 } 234 235 _printer.print(ascii); 236 ++l; 237 if (l == 64 / ascii.length) { 238 l = 0; 239 _printer.println(); 240 } 241 } 242 243 if (l > 0) { 244 _printer.println(); 245 } 246 } 247 248 249 /*** 250 * Reads bytes to fill the specified array unless EOF is encountered. 251 * 252 * @param input Where to read data from 253 * @param buf To be filled from <code>input</code> 254 * @return The number of bytes read from <code>input</code> 255 * into <code>buf</code>, or -1 for EOF. 256 * @exception IOException Description of Exception 257 */ 258 private int fill(InputStream input, byte[] buf) throws IOException { 259 int next = 0; 260 boolean eof = false; 261 262 while (next < buf.length && !eof) { 263 int n = input.read(buf, next, buf.length - next); 264 eof = n < 0; 265 266 if (!eof) { 267 next += n; 268 } 269 } 270 271 return eof ? -1 : next; 272 } 273 274 275 /*** 276 * Inserts plain text into the XML representation, with angle brackets and 277 * amper-ands replaced by the appropriate general entity references. 278 * 279 * @param text Source of the text to escape and insert 280 * @exception IOException Description of Exception 281 */ 282 private void formatText(InputStream text) throws IOException { 283 InputStreamReader isr = new InputStreamReader(text); 284 285 int n; 286 while ((n = isr.read()) != -1) { 287 char c = (char) n; 288 289 switch (c) { 290 case '<': 291 _printer.print("<"); 292 break; 293 case '>': 294 _printer.print(">"); 295 break; 296 case '&': 297 _printer.print("&"); 298 break; 299 default: 300 _printer.print(c); 301 } 302 } 303 } 304 305 306 /*** 307 * Adds a date element at the current position. 308 * 309 * @param date Content of the element 310 */ 311 private void format(Date date) { 312 Calendar cal = Calendar.getInstance(); 313 cal.setTime(date); 314 315 openStartTag("date"); 316 addAttribute("year", Integer.toString(cal.get(Calendar.YEAR))); 317 addAttribute("month", Integer.toString(cal.get(Calendar.MONTH))); 318 addAttribute("day", Integer.toString(cal.get(Calendar.DAY_OF_MONTH))); 319 closeEmptyTag(); 320 } 321 322 323 /*** 324 * Adds a text content version. 325 * 326 * @param content The content to add 327 * @exception InfobitException Description of Exception 328 * @exception InfobitSecurityException Description of Exception 329 * @exception IOException Description of Exception 330 */ 331 private void format(TextContent content) 332 throws InfobitException, InfobitSecurityException, IOException { 333 334 openStartTag("text-content"); 335 closeStartTag(); 336 // TODO: print keywords 337 338 openStartTag("text"); 339 closeStartTag(); 340 InputStream text = _manager.getContent(content); 341 formatText(text); 342 endTag(); 343 // text 344 345 endTag(); 346 // text-content 347 } 348 349 350 /*** 351 * Adds a binary version content. 352 * 353 * @param content The content to add 354 * @exception InfobitException Description of Exception 355 * @exception InfobitSecurityException Description of Exception 356 * @exception IOException Description of Exception 357 */ 358 private void format(BinaryContent content) 359 throws InfobitException, InfobitSecurityException, IOException { 360 361 openStartTag("binary-content"); 362 addAttribute("mime-type", content.getMimeType()); 363 // TODO: format keywords 364 closeStartTag(); 365 366 int contentLength = _manager.getContentLength(content); 367 368 openStartTag("data"); 369 addAttribute("encoding", "base64"); 370 addAttribute("content-length", Integer.toString(contentLength)); 371 closeStartTag(); 372 373 InputStream data = _manager.getContent(content); 374 formatBase64(data); 375 376 endTag(); 377 // data 378 endTag(); 379 // binary-content 380 } 381 382 383 /*** 384 * Adds template version content. 385 * 386 * @param content The content to add 387 * @exception InfobitException Description of Exception 388 * @exception InfobitSecurityException Description of Exception 389 * @exception IOException Description of Exception 390 */ 391 private void format(Template content) 392 throws InfobitException, InfobitSecurityException, IOException { 393 394 openStartTag("template"); 395 closeStartTag(); 396 397 openStartTag("text"); 398 closeStartTag(); 399 400 InputStream text = _manager.getContent(content); 401 formatText(text); 402 403 endTag(); 404 // text 405 endTag(); 406 // template 407 } 408 409 410 /*** 411 * Adds a script content version. 412 * 413 * @param content The content to add 414 */ 415 private void format(Script content) { 416 openStartTag("script"); 417 addAttribute("template-name", content.getTemplate().getName()); 418 closeStartTag(); 419 420 // TODO: add keywords 421 422 for (Iterator i = content.getLinks().entrySet().iterator(); 423 i.hasNext(); ) { 424 425 Map.Entry e = (Map.Entry) i.next(); 426 Infobit value = (Infobit) e.getValue(); 427 428 openStartTag("link"); 429 addAttribute("key", (String) e.getKey()); 430 addAttribute("value-name", value.getName()); 431 closeEmptyTag(); 432 } 433 434 endTag(); 435 } 436 437 438 /*** 439 * Prints the XML representation of a version. 440 * 441 * @param version The version to convert to XML 442 * @exception Exception Description of Exception 443 */ 444 private void format(Version version) throws Exception { 445 openStartTag("version"); 446 addAttribute("name", version.getName()); 447 addAttribute("author", version.getAuthor()); 448 closeStartTag(); 449 450 format(version.getDate()); 451 452 Content content = version.getContent(); 453 454 if (content instanceof TextContent) { 455 format((TextContent) content); 456 } 457 else if (content instanceof BinaryContent) { 458 format((BinaryContent) content); 459 } 460 else if (content instanceof Template) { 461 format((Template) content); 462 } 463 else if (content instanceof Script) { 464 format((Script) content); 465 } 466 else { 467 String m = "Interanal error: unhandled version content" + 468 content.getClass().getName(); 469 _log.fatal(m); 470 throw new RuntimeException(m); 471 } 472 473 endTag(); 474 // version 475 } 476 477 478 /*** 479 * Prints external representation of an infobit. 480 * 481 * @param ib The infobit to convert to XML 482 * @exception Exception Description of Exception 483 */ 484 private void format(Infobit ib) throws Exception { 485 openStartTag("infobit"); 486 addAttribute("name", ib.getName()); 487 488 Version actual = ib.getActualVersion(); 489 if (actual != null) { 490 addAttribute("actual-version-name", actual.getName()); 491 } 492 493 closeStartTag(); 494 495 for (Iterator i = ib.getVersions().iterator(); i.hasNext(); ) { 496 Version version = (Version) i.next(); 497 format(version); 498 } 499 500 endTag(); 501 } 502 503 504 /*** 505 * Prints left indentation appropriate to element stack and extra attribute 506 * indentation. 507 */ 508 private void indent() { 509 // PRE: at start of line 510 511 int l; 512 switch (_formatState) { 513 case FS_START: 514 l = _elementStack.size() * TAB_WIDTH; 515 break; 516 case FS_OPEN_CONT: 517 { 518 int n = _elementStack.size(); 519 String tos = (String) _elementStack.get(n - 1); 520 l = (n - 1) * TAB_WIDTH + 1 + tos.length() + 1; 521 } 522 break; 523 case FS_OPEN_INIT: 524 default: 525 _log.error("Illegal state for indentation: FS_OPEN_START"); 526 throw new RuntimeException(); 527 } 528 529 for (int i = l; i-- > 0; ) { 530 _printer.print(" "); 531 } 532 } 533 534 535 /*** 536 * Opens a start tag, properly handling indentation. 537 * 538 * @param element Name of the element to start 539 */ 540 private void openStartTag(String element) { 541 // PRE: _formatState = _START 542 indent(); 543 _printer.print("<" + element); 544 _elementStack.add(element); 545 _formatState = FS_OPEN_INIT; 546 } 547 548 549 /*** 550 * Adds an attribute to an already opened start tag. 551 * 552 * @param name Name of the attribute to add 553 * @param value Value of the attribute 554 */ 555 private void addAttribute(String name, String value) { 556 switch (_formatState) { 557 case FS_OPEN_INIT: 558 _printer.print(" "); 559 break; 560 case FS_OPEN_CONT: 561 _printer.println(); 562 indent(); 563 default: 564 // runtime error 565 } 566 567 _printer.print(name + "="); 568 569 if (value.indexOf('"') == -1) { 570 _printer.print("\"" + value + "\""); 571 } 572 else if (value.indexOf('\'') == -1) { 573 _printer.print("'" + value + "'"); 574 } 575 else { 576 _printer.print("\""); 577 _printer.print(value.replaceAll("\"", """)); 578 _printer.print("\""); 579 } 580 581 _formatState = FS_OPEN_CONT; 582 } 583 584 585 /*** 586 * Closes the most recently opened start tag. 587 */ 588 private void closeStartTag() { 589 _printer.print(">"); 590 _printer.println(); 591 _formatState = FS_START; 592 } 593 594 595 /*** 596 * Closes the most recently opened tag, leaving it as an empty element tag. 597 */ 598 private void closeEmptyTag() { 599 _elementStack.remove(_elementStack.size() - 1); 600 _printer.print("/>"); 601 _printer.println(); 602 _formatState = FS_START; 603 } 604 605 606 /*** 607 * Closes the most recently opened start tag. 608 */ 609 private void endTag() { 610 // PRE: _formatState == FS_START 611 String n = (String) _elementStack.remove(_elementStack.size() - 1); 612 indent(); 613 _printer.println("</" + n + ">"); 614 } 615 616 } 617

This page was automatically generated by Maven