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