KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > xml > DataEncoder


1 /* ****************************************************************************
2  * DataEncoder.java
3  *
4  * Compile XML directly to SWF bytecodes.
5  * ****************************************************************************/

6
7 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
8 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
9 * Use is subject to license terms. *
10 * J_LZ_COPYRIGHT_END *********************************************************/

11
12 package org.openlaszlo.xml;
13
14 import java.io.*;
15 import java.util.*;
16
17 import org.xml.sax.*;
18
19 import org.openlaszlo.xml.internal.*;
20 import org.openlaszlo.iv.flash.util.*;
21 import org.openlaszlo.iv.flash.api.action.*;
22 import org.openlaszlo.iv.flash.api.*;
23 import org.openlaszlo.compiler.CompilationError;
24 import org.openlaszlo.utils.ChainedException;
25 import org.openlaszlo.utils.FileUtils;
26 import org.openlaszlo.utils.HashIntTable;
27
28 import org.jdom.input.SAXBuilder;
29 import org.jdom.output.SAXOutputter;
30 import org.jdom.JDOMException;
31 import org.jdom.Document;
32 import org.jdom.Attribute;
33 import org.jdom.Element;
34 import org.xml.sax.Attributes JavaDoc;
35 import org.xml.sax.helpers.AttributesImpl JavaDoc;
36
37 import org.apache.log4j.*;
38
39
40 /**
41  * API to compile XML-like data sets to SWF bytecode format.
42  *<p>
43  * This class implements the SAX ContentHandler API, and could possibly be used
44  * directly with a SAX Parser, but is designed to be used manually as shown below.
45  * <p>
46  * Example usage:
47  * <pre>
48  * import org.xml.sax.helpers.AttributesImpl;
49  *
50  * // send simple row data as node with attributes
51  * InputStream getDataInputStream(Request req, Response resp) {
52  * Connection conn = DriverManager.getConnection(ConnectionPoolManager.URL_PREFIX + alias, null, null);
53  * Statement stmt = conn.createStatement();
54  * ResultSet resultset = stmt.executeQuery("SELECT NAME, AGE, ID FROM EMPLOYEES");
55  * AttributesImpl emptyAttr = new AttributesImpl();
56  *
57  *
58  * DataEncoder db = new DataEncoder();
59  * db.startDocument();
60  * db.startElement("results", emptyAttr);
61  *
62  * while (resultset.next()) {
63  * AttributesImpl attrs = new AttributesImpl();
64  * //Output the values as attributes
65  * attrs.addAttribute("", "name", "", "CDATA", resultset.getString("name"));
66  * attrs.addAttribute("", "age", "", "CDATA", resultset.getString("age"));
67  * attrs.addAttribute("", "id", "", "CDATA", resultset.getString("id"));
68  * db.startElement("row", attrs);
69  * db.endElement();
70  * }
71  * db.endElement();
72  * db.endDocument();
73  *
74  * OutputStream out = resp.getOutputStream();
75  * return db.getInputStream();
76  * }
77  * </pre>
78  */

79 public class DataEncoder implements org.xml.sax.ContentHandler JavaDoc {
80
81     /* Logger */
82     private static Logger mLogger = Logger.getLogger(DataEncoder.class);
83
84     /** Hint to allocate buffer size large enough to hold output. */
85     private int initsize = 0;
86     private static final int DEFAULT_BUFFER_SIZE = 4096;
87
88     /**
89      * The SWF file
90      */

91     private FlashOutput mSWF = null;
92     /**
93      * Size of the SWF file.
94      */

95     private long mSize = -1;
96
97     // Target version of Flash to compile to
98
int mFlashVersion = 6;
99
100     /**
101      * Constructs an empty DataEncoder.
102      */

103     public DataEncoder () { }
104
105     /**
106      * Constructs a DataEncoder with a buffer allocation size hint.
107      * @param initsize hint to allocate buffer size large enough to hold output.
108      */

109     public DataEncoder (int initsize) {
110         this.initsize = initsize;
111     }
112
113     //============================================================
114
// SAX API
115
//============================================================
116

117     /**
118      * Receive notification of character data.
119      *
120      * @param ch the characters from the XML document.
121      * @param start the start position in the array.
122      * @param length the number of characters to read from the array.
123      *
124      * @see #characters(String) characters(String)
125      */

126     public void characters(char[] ch, int start, int length) {
127         String JavaDoc text = new String JavaDoc(ch, start, length);
128         characters(text);
129     }
130
131     /**
132      * Receive notification of string data.
133      *
134      * @param text the string from the XML document.
135      *
136      * @see #characters(char[], int, int) characters(char[], int, int)
137      */

138     public void characters(String JavaDoc text) {
139         // makeTextNode = function (text, parent)
140
// dup pointer to parent (who is at top of stack)
141
// DUP
142
body.writeByte(Actions.PushDuplicate);
143         // Push text
144

145
146         body.writeByte(Actions.PushData);
147         // Leave a two byte space for the PUSH length
148
// Mark where we are, so we can rewrite this with correct length later.
149
int push_bufferpos = body.getPos();
150         body.writeWord(0); // placeholder 16-bit length field
151

152         DataCommon.pushMergedStringData(text, body, dc);
153         // Set up argsnum and function name
154
// PUSH 2, _tdn
155
body.writeByte(0x07); // INT type
156
body.writeDWord(2); // '2' integer constant ; number of args to function
157
body.writeByte(0x08); // SHORT DICTIONARY LOOKUP
158
body.writeByte(textnode_idx); // push function name: index of "_t" string constant
159

160         // Now go back and fix up the size arg to the PUSH instruction
161
int total_size = body.getPos() - (push_bufferpos + 2);
162         //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
163
body.writeWordAt(total_size, push_bufferpos);
164
165         body.writeByte(Actions.CallFunction);
166         // Pop the node, because there will be no end tag for it, and it has no children.
167
body.writeByte(Actions.Pop);
168     }
169
170
171     /**
172      * Receive notification of the end of an element. This method is equivalent
173      * to calling {@link #endElement() endElement()} -- the input parameters are
174      * ignored.
175      *
176      * @param uri the Namespace URI, or the empty string if the element has no
177      * Namespace URI or if Namespace processing is not being performed.
178      * @param localName the local name (without prefix), or the empty string if
179      * Namespace processing is not being performed.
180      * @param qName the qualified XML 1.0 name (with prefix), or the empty
181      * string if qualified names are not available.
182      * @see #endElement() endElement()
183      */

184     public void endElement(java.lang.String JavaDoc uri, java.lang.String JavaDoc localName, java.lang.String JavaDoc qName) {
185         // Pop the node off the stack.
186
body.writeByte(Actions.Pop);
187     }
188
189     /**
190      * Receive notification of the end of an element.
191      *
192      * @see #endElement(String,String,String) endElement(String,String,String)
193      */

194     public void endElement() {
195         // Pop the node off the stack.
196
body.writeByte(Actions.Pop);
197     }
198
199
200     /**
201      * End the scope of a prefix-URI mapping. This method is unimplemented.
202      *
203      * @param prefix the prefix that was being mapped.
204      */

205     public void endPrefixMapping(java.lang.String JavaDoc prefix) {
206     }
207
208     /**
209      * Receive notification of ignorable whitespace in element content. This
210      * method is unimplemented.
211      *
212      * @param ch the characters from the XML document.
213      * @param start the start position in the array.
214      * @param length the number of characters to read from the array.
215      */

216     public void ignorableWhitespace(char[] ch, int start, int length) {
217     }
218
219     /**
220      * Receive notification of a processing instruction. This method is
221      * unimplemented.
222      *
223      * @param target the processing instruction target.
224      * @param data the processing instruction data, or null if none was
225      * supplied. The data does not include any whitespace separating it from the
226      * target.
227      */

228     public void processingInstruction(java.lang.String JavaDoc target, java.lang.String JavaDoc data) {
229     }
230
231     /**
232      * Receive an object for locating the origin of SAX document events. This
233      * method is unimplemented.
234      *
235      * @param locator an object that can return the location of any SAX document
236      * event.
237      */

238     public void setDocumentLocator(Locator locator) {
239     }
240
241     /**
242      * Receive notification of a skipped entity. This method is unimplemented.
243      *
244      * @param name the name of the skipped entity. If it is a parameter entity,
245      * the name will begin with '%', and if it is the external DTD subset, it
246      * will be the string "[dtd]".
247      */

248     public void skippedEntity(java.lang.String JavaDoc name) {
249     }
250
251     /** State vars */
252     DataContext dc;
253     // The node constructor function name.
254
private byte constructor_idx;
255     private byte textnode_idx;
256
257     FlashBuffer body;
258     Program program;
259     Program resultProgram;
260     FlashBuffer out;
261
262     /**
263      * Receive notification of the beginning of a document.
264      *
265      * @see #endDocument() endDocument()
266      */

267     public void startDocument() {
268         dc = new DataContext(mFlashVersion);
269         if (mFlashVersion == 5) {
270             dc.setEncoding("Cp1252");
271         } else {
272             dc.setEncoding("UTF-8");
273         }
274         constructor_idx = (byte) (DataCommon.addStringConstant(DataCommon.NODE_INSTANTIATOR_FN, dc) & 0xFF);
275         textnode_idx = (byte) (DataCommon.addStringConstant(DataCommon.TEXT_INSTANTIATOR_FN, dc) & 0xFF);
276
277         // Room at the end of the buffer for maybe some callback code to the runtime to say we're done.
278
// Allocate enough room to hold the data nodes and strings ; it should be < input XML filesize
279
body = new FlashBuffer(initsize == 0 ? DEFAULT_BUFFER_SIZE : initsize);
280         program = new Program(body);
281
282         // Bind the node creation functions to some short local names:
283
// element nodes: _root._m => _m
284
program.push(new Object JavaDoc[]{"_m", "_root"});
285         program.getVar();
286         program.push("_m");
287         body.writeByte(Actions.GetMember);
288         program.setVar();
289
290         // text nodes: _root._t => _t
291
program.push(new Object JavaDoc[]{"_t", "_root"});
292         program.getVar();
293         program.push("_t");
294         body.writeByte(Actions.GetMember);
295         program.setVar();
296
297         // Build a root node by calling the runtime's root node instantiator
298
//
299
// The root node will have $n = 0, and whatever other initial conditions are needed.
300
program.push(0); // Root node creator function takes no args.
301
program.push("_root");
302         program.getVar();
303         program.push(DataCommon.ROOT_NODE_INSTANTIATOR_FN);
304         program.callMethod();
305         // The root node is now on the stack.
306
// Build data. Invariant is that it leaves the stack the way it found it.
307

308         AttributesImpl JavaDoc emptyAttr = new AttributesImpl JavaDoc();
309         startElement("resultset", emptyAttr);
310         startElement("body", emptyAttr);
311     }
312
313     /**
314      * Receive notification of the end of a document.
315      *
316      * @see #startDocument() startDocument()
317      */

318     public void endDocument() {
319         
320         endElement();
321         // NOTE: [2003-07-17 bloch] This is where headers/meta-data would be inserted.
322
endElement();
323
324         // The root node is sitting on the stack.
325
// Finalize; bind the variable "root" to the node that the lfc expects.
326
program.push(1);
327         program.push("_root");
328         program.getVar();
329         program.push(DataCommon.ROOT_NODE_FINAL_FN);
330         program.callMethod();
331
332         FlashBuffer body = program.body();
333
334         // Bind "root" global to the top node
335
body.writeByte(Actions.PushDuplicate);
336         program.push("__lztmproot");
337         body.writeByte(Actions.StackSwap);
338         program.setVar();
339
340         // Call into viewsystem
341
program.push("_parent");
342         program.getVar();
343         program.push(2);
344         program.push("_parent");
345         program.getVar();
346         program.push("loader");
347         body.writeByte(Actions.GetMember);
348         program.push("returnData");
349         program.callMethod();
350         program.pop();
351
352         // Collect the string dictionary data
353
byte pooldata[] = DataCommon.makeStringPool(dc);
354         // 'out' is the main FlashBuffer for composing the output file
355
final int MISC = 64;
356         out = new FlashBuffer(body.getSize() + pooldata.length + MISC);
357         // Write out string constant pool
358
out.writeByte( Actions.ConstantPool );
359         out.writeWord( pooldata.length + 2 ); // number of bytes in pool data + int (# strings)
360
out.writeWord( dc.cpool.size() ); // number of strings in pool
361
out.writeArray( pooldata, 0, pooldata.length); // copy the data
362
// Write out the code to build nodes
363
out.writeArray(body.getBuf(), 0, body.getSize());
364         resultProgram = new Program(out);
365     }
366
367     /**
368      * @return the Flash program to build the data set.
369      */

370     private Program getProgram() {
371         return resultProgram;
372     }
373
374
375     /**
376      * Make SWF
377      *
378      * @return FlashFile containing entire SWF with header
379      */

380     private FlashFile makeSWFFile() {
381         // Create FlashFile object nd include action bytes
382
FlashFile file = FlashFile.newFlashFile();
383         Script s = new Script(1);
384         file.setMainScript(s);
385         file.setVersion(5);
386         Frame frame = s.newFrame();
387         frame.addFlashObject(new DoAction(resultProgram));
388         return file;
389     }
390
391     /*
392     public int writeSWF(OutputStream w, boolean closeStream)
393         throws IOException {
394         // Get inputstream and write to outputstream
395         InputStream input;
396         int i;
397         try {
398             FlashFile file = makeSWFFile();
399             input = file.generate().getInputStream();
400             i = FileUtils.send(input, w);
401             w.flush();
402         } catch (IVException ex) {
403             throw new ChainedException(ex);
404         } finally {
405             if (closeStream) {
406                 w.close();
407             }
408         }
409         return i;
410     }
411     */

412
413
414     /**
415      * Get the compiled data swf program byte codes. Only call this after you
416      * have called {@link #endDocument endDocument()}.
417      *
418      * @return input stream containing the compiled SWF data program; only valid
419      * after {@link #endDocument endDocument()} has been called. Must not be called
420      * before {@link #endDocument endDocument()}.
421      */

422     public InputStream getInputStream()
423         throws IOException {
424
425         generate();
426         return mSWF.getInputStream();
427     }
428
429     /**
430      * Return the size of the output object; only valid after endDocument
431      * {@link #endDocument endDocument()} has been called. Must not be called
432      * before {@link #endDocument endDocument()}.
433      *
434      * @return long representing the size
435      */

436     public long getSize()
437         throws IOException {
438
439         generate();
440         return mSize;
441     }
442
443     /**
444      * Generate the SWF file
445      */

446     private void generate() throws IOException {
447
448         if (mSWF == null) {
449             try {
450                 InputStream input;
451                 FlashFile file = makeSWFFile();
452                 mSWF = file.generate();
453                 mSize = mSWF.pos;
454             } catch (IVException ex) {
455                 throw new ChainedException(ex);
456             }
457         }
458     }
459
460     /**
461      * A lower level call than startElement(); attributes must be supplied by
462      * individual calls to addAttribute(). This method is unimplemented.
463      * @param localName the element name.
464      */

465     public void _startElement (String JavaDoc localName) {
466
467     }
468
469     /**
470      * A low level call to add an attribute, must be preceded by call to
471      * _startElement() for a given element. This method is unimplemented.
472      */

473     public void addAttribute (String JavaDoc attrName, String JavaDoc attrVal) {
474
475     }
476
477
478     /**
479      * Receive notification of the beginning of an element. This method is
480      * equivalent to calling {@link #startElement(String, Attributes)
481      * startElement(String, Attributes)} -- the uri and qName parameters are
482      * ignored.
483      *
484      * @param uri the Namespace URI, or the empty string if the element has no
485      * Namespace URI or if Namespace processing is not being performed.
486      * @param localName the local name (without prefix), or the empty string if
487      * Namespace processing is not being performed.
488      * @param qName the qualified name (with prefix), or the empty string if
489      * qualified names are not available.
490      * @param atts the attributes attached to the element. If there are no
491      * attributes, it shall be an empty Attributes object.
492      *
493      * @see #startElement(String, Attributes) startElement(String, Attributes)
494      */

495     public void startElement(java.lang.String JavaDoc uri, java.lang.String JavaDoc localName, java.lang.String JavaDoc qName, Attributes JavaDoc atts) {
496         startElement(localName, atts);
497     }
498
499
500     /**
501      * Receive notification of the beginning of an element.
502      *
503      * @param localName the local name (without prefix), or the empty string if
504      * Namespace processing is not being performed.
505      * @param atts the attributes attached to the element. If there are no
506      * attributes, it shall be an empty Attributes object.
507      *
508      * @see #startElement(String, String, String, Attributes)
509      * startElement(String, String, String, Attributes)
510      */

511     public void startElement(java.lang.String JavaDoc localName, Attributes JavaDoc atts) {
512         int idx; // tmp var to hold a string pool index
513
// makeNodeNoText = function (attrs, name, parent)
514
// dup pointer to PARENT (who is at top of stack)
515
// DUP
516
body.writeByte(Actions.PushDuplicate);
517
518         // We're really squeezing things down, so we are going to merge
519
// the PUSH of the element name with the PUSH of the attribute key/value
520
// data and the attribute count. So the stack will look like
521
// [eltname attrname1 attrval1 attrname2 attrval2 ... ... nattrs]
522
// when we're done
523
body.writeByte(Actions.PushData);
524         // Leave a two byte space for the PUSH length
525
// Mark where we are, so we can rewrite this with correct length later.
526
int push_bufferpos = body.getPos();
527         body.writeWord(0); // placeholder 16-bit length field
528

529         // Push element NAME
530
String JavaDoc eltname = localName;
531         DataCommon.pushMergedStringDataSymbol(eltname, body, dc);
532
533         // Fold all the attribute key/value pairs into a single PUSH
534
// Build ATTRIBUTE object
535
int nattrs = atts.getLength();
536
537         // PUSH := {0x96, lenlo, lenhi, 0x00, char, char, char, ...0x00, }
538
for (int i = 0; i < nattrs; i++) {
539             String JavaDoc attrname = atts.getLocalName(i);
540             //System.out.print("Attr " + attrname);
541
DataCommon.pushMergedStringDataSymbol(attrname, body, dc);
542
543             String JavaDoc attrval = atts.getValue(i);
544             //System.out.println("= " + attrval);
545
DataCommon.pushMergedStringData(attrval, body, dc);
546         }
547         // create the attrs object; push the attr count
548
body.writeByte(0x07); // INT type
549
body.writeDWord(nattrs);
550         // Now go back and fix up the size arg to the PUSH instruction
551
int total_size = body.getPos() - (push_bufferpos + 2);
552         //System.out.println("pos="+body.getPos()+ " total_size = "+total_size+" push_bufferpos="+push_bufferpos+" nattrs="+nattrs);
553
body.writeWordAt(total_size, push_bufferpos);
554         body.writeByte(Actions.InitObject);
555
556         // stack now has [parent, name, attrs]
557
// Push # of args and node-instantiator-function name
558
// PUSH 3, _mdn
559
// [PUSHDATA, 7, 0, 0x07, 03 00 00 00 0x08, constructor_idx]
560
body.writeByte(Actions.PushData);
561         body.writeWord(7);
562         body.writeByte(0x07); // INT type
563
body.writeDWord(3); // '3' integer constant , number of args to node constructor fn
564
body.writeByte(0x08); // SHORT DICTIONARY LOOKUP type
565
body.writeByte(constructor_idx); // index of "_m" string constant
566
body.writeByte(Actions.CallFunction);
567         // We leave the new node on the stack, so we can reference it as the parent
568
// Stack => [parentnode newnode]
569
}
570
571     /**
572      * Begin the scope of a prefix-URI Namespace mapping. This method is
573      * unimplemented.
574      *
575      * @param prefix the Namespace prefix being declared.
576      * @param uri the Namespace URI the prefix is mapped to.
577      */

578     public void startPrefixMapping(java.lang.String JavaDoc prefix, java.lang.String JavaDoc uri) {
579     }
580
581
582     //============================================================
583
// End SAX ContentHandler Compatibility
584
//============================================================
585

586
587     /**
588      * Parse a DOM Document and pass it to DataEncoder via JDOM SAXOutputter
589      *
590      * @param doc DOM Document to build.
591      * @throws DataCompilerException if there was a problem compiling the Document.
592      */

593     public void buildFromDocument(Document doc) throws DataEncoderException {
594         try {
595             SAXOutputter saxout = new SAXOutputter(this);
596             saxout.output(doc);
597         }
598         catch (JDOMException e) {
599             throw new DataEncoderException("Error compiling XML from Document: "+e.getMessage());
600         }
601     }
602
603     /**
604      * Build from a DOM Element.
605      *
606      * @param e DOM Element.
607      */

608     public void buildFromElement(Element e) {
609         startDocument();
610         traverseDOM(e);
611         endDocument();
612     }
613
614    String JavaDoc getQualifiedName(Attribute attr)
615    {
616        // AttributesImpl expects a full qualified name or the empty string, if
617
// qualified name is not available.
618

619        if (attr.getNamespacePrefix().equals(""))
620            return "";
621        return attr.getQualifiedName();
622    }
623
624    String JavaDoc getAttributeType(Attribute attr)
625    {
626        // Not really sure if returning an "ENUMERATED" string is the right
627
// thing to pass back for enumerated types.
628

629        int type = attr.getAttributeType();
630        if ( type == Attribute.CDATA_ATTRIBUTE) { return "CDATA"; }
631        else if ( type == Attribute.ID_ATTRIBUTE) { return "ID"; }
632        else if ( type == Attribute.IDREF_ATTRIBUTE) { return "IDREF"; }
633        else if ( type == Attribute.IDREFS_ATTRIBUTE) { return "IDREFS"; }
634        else if ( type == Attribute.ENTITY_ATTRIBUTE) { return "ENTITY"; }
635        else if ( type == Attribute.ENTITIES_ATTRIBUTE) { return "ENTITIES"; }
636        else if ( type == Attribute.NMTOKEN_ATTRIBUTE) { return "NMTOKEN"; }
637        else if ( type == Attribute.NMTOKENS_ATTRIBUTE) { return "NMTOKENS"; }
638        else if ( type == Attribute.NOTATION_ATTRIBUTE) { return "NOTATION"; }
639        else if ( type == Attribute.ENUMERATED_ATTRIBUTE) { return "ENUMERATED"; }
640        else { return ""; }
641        
642    }
643
644     private void traverseDOM(Element el) {
645         List attrList = el.getAttributes();
646         AttributesImpl JavaDoc attrs = new AttributesImpl JavaDoc();
647         for (int i=0; i < attrList.size(); i++) {
648             Attribute attr =(Attribute)attrList.get(i);
649             attrs.addAttribute(attr.getNamespaceURI(), attr.getName(),
650                                getQualifiedName(attr), getAttributeType(attr),
651                                attr.getValue());
652         }
653
654         startElement(el.getName(), attrs);
655
656         // Add text node
657
String JavaDoc text = el.getTextTrim();
658         if (text != null && text.length() != 0)
659             characters(text);
660
661         List children = el.getChildren();
662         for (int i=0; i < children.size(); i++)
663             traverseDOM((Element)children.get(i));
664
665         endElement();
666
667     }
668
669
670 }
671
672
Popular Tags