    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