KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > sc > ScriptCompiler


1 /* *****************************************************************************
2  * ScriptCompiler.java
3  * ****************************************************************************/

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

9
10 package org.openlaszlo.sc;
11 import java.io.*;
12 import java.util.*;
13 import org.openlaszlo.server.LPS;
14 import org.openlaszlo.utils.ChainedException;
15 import org.openlaszlo.iv.flash.api.*;
16 import org.openlaszlo.iv.flash.api.action.*;
17 import org.openlaszlo.iv.flash.util.*;
18 import org.apache.log4j.*;
19 import org.openlaszlo.cache.Cache;
20 import org.openlaszlo.utils.FileUtils;
21 import org.openlaszlo.compiler.CompilationError;
22
23 /** Utility class for compiling scripts, translating Java objects
24  * (maps, lists, and strings) to source expressions for the
25  * corresponding JavaScript object.
26  *
27  * @author Oliver Steele
28  */

29 public class ScriptCompiler extends Cache {
30     private static Logger mLogger = Logger.getLogger(ScriptCompiler.class);
31     
32     static public final String JavaDoc SCRIPT_CACHE_NAME = "scache";
33
34     /** Map(String properties+script, byte[] bytes), or null if the
35      * cache hasn't been initialized, has been cleared, or the cache
36      * size is zero. */

37     // TODO: [2002-11-28 ows] use org.apache.commons.util.BufferCache?
38
// TODO: [2002-11-28 ows] wrap in Collections.synchronizedMap?
39
private static ScriptCompiler mScriptCache = null;
40     
41     public ScriptCompiler(String JavaDoc name, File cacheDirectory, Properties props)
42         throws IOException {
43         super(name, cacheDirectory, props);
44     }
45
46     public static ScriptCompiler getScriptCompilerCache() {
47         return mScriptCache;
48     }
49
50     public static synchronized ScriptCompiler initScriptCompilerCache(File cacheDirectory, Properties initprops)
51       throws IOException {
52         if (mScriptCache != null) {
53             return mScriptCache;
54         }
55         mScriptCache = new ScriptCompiler(SCRIPT_CACHE_NAME, cacheDirectory, initprops);
56         return mScriptCache;
57     }
58
59     /**
60      * Compiles the ActionScript in script to a new movie in the swf
61      * file named by outfile.
62      *
63      * @param script a <code>String</code> value
64      * @param outfile a <code>File</code> value
65      */

66     public static void compile(String JavaDoc script, File outfile, int swfversion)
67         throws IOException
68     {
69         FileOutputStream ostream = new FileOutputStream(outfile);
70         compile(script, ostream, swfversion);
71         ostream.close();
72     }
73
74     /**
75      * Compile the ActionScript in script to a movie, that's written
76      * to output.
77      *
78      * @param script a <code>String</code> value
79      * @param ostream an <code>OutputStream</code> value
80      */

81     public static void compile(String JavaDoc script, OutputStream ostream, int swfversion)
82         throws IOException
83     {
84         byte[] action = compileToByteArray(script, new Properties());
85         writeScriptToStream(action, ostream, swfversion);
86     }
87
88     /*
89     // TODO: [2004-01-07 hqm] This cache clearing method has the
90     following bug now; if the in memory ScriptCache has not been
91     created yet when clearCache is called, then the disk cache won't
92     get cleared. We need to make sure mScriptCache is initialized at
93     server startup.]
94     */

95     public static boolean clearCacheStatic() {
96         if (mScriptCache != null) {
97             return mScriptCache.clearCache();
98         }
99         return false;
100     }
101
102
103     private static byte[] _compileToByteArray(String JavaDoc script,
104                                               Properties properties) {
105         org.openlaszlo.sc.Compiler compiler =
106             new org.openlaszlo.sc.Compiler();
107         compiler.setProperties(properties);
108         try {
109             return compiler.compile(script);
110         } catch (org.openlaszlo.sc.parser.TokenMgrError e) {
111             // The error message isn't helpful, and has the wrong
112
// source location in it, so ignore it.
113
// TODO: [2003-01-09 ows] Fix the error message.
114
throw new CompilerException("Lexical error. The source location is for the element that contains the erroneous script. The error may come from an unterminated comment.");
115         }
116     }
117
118     /**
119      * @return a cache key for the given properties
120      */

121     static String JavaDoc sortedPropertiesList(Properties props) {
122         TreeSet sorted = new TreeSet ();
123         for (java.util.Enumeration JavaDoc e = props.propertyNames();
124              e.hasMoreElements(); ) {
125             String JavaDoc key = (String JavaDoc) e.nextElement();
126
127             String JavaDoc value = props.getProperty(key);
128             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
129             buf.append(key);
130             buf.append(' ');
131             buf.append(value);
132
133             sorted.add(buf.toString());
134         }
135         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
136         for (java.util.Iterator JavaDoc e = sorted.iterator(); e.hasNext(); ) {
137             String JavaDoc str = (String JavaDoc) e.next();
138             buf.append(str);
139         }
140         String JavaDoc propstring = buf.toString();
141         return propstring;
142     }
143
144
145
146     /** Compiles the specified script into bytecode
147      *
148      * @param script a script
149      * @return an array containing the bytecode
150      */

151     public static byte[] compileToByteArray(String JavaDoc script,
152                                             Properties properties) {
153         mLogger.debug(script);
154         // We only want to keep off the properties that affect the
155
// compilation. Currently, "filename" is the only property
156
// that tends to change and that the script compiler ignores,
157
// so make a copy of properties that neutralizes that.
158
properties = (Properties) properties.clone();
159         properties.setProperty("filename", "");
160         // The key is a string representation of the arguments:
161
// properties, and the script.
162
StringWriter writer = new StringWriter();
163         writer.write(sortedPropertiesList(properties));
164         writer.getBuffer().append(script);
165         String JavaDoc key = writer.toString();
166         // Check the cache. clearCache may clear the cache at any
167
// time, so use a copy of it so that it doesn't change state
168
// between a test that it's null and a method call on it.
169
ScriptCompiler cache = mScriptCache;
170         Item item = null;
171         byte[] code = null;
172         try {
173             if (mScriptCache == null) {
174                 return _compileToByteArray(script, properties);
175             } else {
176                 synchronized (mScriptCache) {
177                     item = mScriptCache.findItem(key, null, false);
178                 }
179             }
180
181             if (item.getInfo().getSize() != 0) {
182                 code = item.getRawByteArray();
183             } else {
184                 code = _compileToByteArray(script, properties);
185                 // Another thread might already have set this since we
186
// called get. That's okay --- it's the same value.
187
synchronized (mScriptCache) {
188                     item.update(new ByteArrayInputStream(code), null);
189                     item.updateInfo();
190                     item.markClean();
191                 }
192             }
193             
194             mScriptCache.updateCache(item);
195
196             return (byte[]) code;
197         } catch (IOException e) {
198             throw new CompilationError(e, "IOException in compilation/script-cache");
199         }
200     }
201     
202     /**
203      * @param action actionscript byte codes
204      * @param ostream outputstream to write SWF
205      */

206     public static void writeScriptToStream(byte[] action,
207            OutputStream ostream, int swfversion) throws IOException {
208         FlashFile file = FlashFile.newFlashFile();
209         Script s = new Script(1);
210         file.setMainScript(s);
211         file.setVersion(swfversion);
212         Program program = new Program(action, 0, action.length);
213         Frame frame = s.newFrame();
214         frame.addFlashObject(new DoAction(program));
215         InputStream input;
216         try {
217             input = file.generate().getInputStream();
218         }
219         catch (IVException e) {
220             throw new ChainedException(e);
221         }
222
223         byte[] buffer = new byte[1024];
224         int b = 0;
225         while((b = input.read(buffer)) > 0) {
226             ostream.write(buffer, 0, b);
227         }
228     }
229
230
231
232     /** Writes a LaszloScript expression that evaluates to a
233      * LaszloScript representation of the object.
234      *
235      * @param elt an element
236      * @param writer a writer
237      * @throws java.io.IOException if an error occurs
238      */

239     public static void writeObject(Object JavaDoc object, java.io.Writer JavaDoc writer)
240         throws java.io.IOException JavaDoc
241     {
242         if (object instanceof Map) {
243             writeMap((Map) object, writer);
244         } else if (object instanceof List) {
245             writeList((List) object, writer);
246         } else {
247             writer.write(object.toString());
248         }
249     }
250
251     /** Writes a LaszloScript object literal whose properties are the
252      * keys of the map and whose property values are the LaszloScript
253      * representations of the map's values.
254      *
255      * The elements of the map are strings that represent JavaScript
256      * expressions, not values. That is, the value "foo" will compile
257      * to a reference to the variable named foo; "'foo'" or "\"foo\""
258      * is necessary to enter a string in the map.
259      *
260      * @param map String -> Object
261      * @param writer a writer
262      * @return a string
263      */

264     private static void writeMap(Map map, java.io.Writer JavaDoc writer)
265         throws java.io.IOException JavaDoc
266     {
267         writer.write("{");
268         // Sort the keys, so that regression tests aren't sensitive to
269
// the undefined order of iterating a (non-TreeMap) Map.
270
SortedMap smap = new TreeMap(map);
271         for (Iterator iter = smap.entrySet().iterator(); iter.hasNext(); ) {
272             Map.Entry entry = (Map.Entry) iter.next();
273             String JavaDoc key = (String JavaDoc) entry.getKey();
274             Object JavaDoc value = entry.getValue();
275             if (!isIdentifier(key))
276                 key = quote(key);
277             writer.write(key + ": ");
278             writeObject(value, writer);
279             if (iter.hasNext()) {
280                 writer.write(", ");
281             }
282         }
283         writer.write("}");
284     }
285     
286     /** Writes a LaszloScript array literal that evaluates to a
287      * LaszloScript array whose elements are LaszloScript
288      * representations of the arguments elements.
289      *
290      * The elements of the list are strings that represent JavaScript
291      * expressions, not values. That is, the value "foo" will compile
292      * to a reference to the variable named foo; "'foo'" or "\"foo\""
293      * is necessary to enter a string in the array.
294      *
295      * @param list a list
296      * @param writer a writer
297      * @return a string
298      */

299     private static void writeList(List list, java.io.Writer JavaDoc writer)
300         throws java.io.IOException JavaDoc
301     {
302         writer.write("[");
303         for (java.util.Iterator JavaDoc iter = list.iterator();
304              iter.hasNext(); ) {
305             writeObject(iter.next(), writer);
306             if (iter.hasNext()) {
307                 writer.write(", ");
308             }
309         }
310         writer.write("]");
311     }
312     
313     /** Returns true iff the string is a valid JavaScript identifier. */
314     public static boolean isIdentifier(String JavaDoc s) {
315         if (s.length() == 0)
316             return false;
317         if (!Character.isJavaIdentifierStart(s.charAt(0)))
318             return false;
319         for (int i = 1; i < s.length(); i++)
320             if (!Character.isJavaIdentifierPart(s.charAt(i)))
321                 return false;
322         s = s.intern();
323         String JavaDoc[] keywords = {"break", "continue", "delete", "else", "for", "function", "if", "in", "instanceof", "new", "return", "this", "typeof", "var", "void", "while", "with", "case", "catch", "class", "const", "debugger", "default", "do", "enum", "export", "extends", "finally", "import", "super", "switch", "throw", "try"};
324         for (int i = 0; i < keywords.length; i++) {
325             if (s == keywords[i])
326                 return false;
327         }
328         return true;
329     }
330     
331     /** Enclose the specified string in double-quotes, and character-quote
332      * any characters that need it.
333      * @param s a string
334      * @return a quoted string
335      */

336     public static String JavaDoc quote(String JavaDoc s) {
337         try {
338             final char CHAR_ESCAPE = '\\';
339             java.io.StringReader JavaDoc reader = new java.io.StringReader JavaDoc(s);
340             java.io.StringWriter JavaDoc writer = new java.io.StringWriter JavaDoc();
341             writer.write('\"');
342             int i;
343             while ((i = reader.read()) != -1) {
344                 char c = (char) i;
345                 switch (c) {
346                     // todo: look up other characters
347
case '\n':
348                     writer.write("\\n");
349                     break;
350                 case '\r':
351                     writer.write("\\r");
352                     break;
353                 case '\\':
354                 case '\"':
355                     // quote...
356
writer.write(CHAR_ESCAPE);
357                     writer.write(c);
358                     break;
359                 default:
360                     if (i < 32 || i >= 256) {
361                         /*
362                           throw new RuntimeException("can't quote char #" + i +
363                                                    " in \"" + s + "\"");
364                         */

365                         // ECMAScript string literal hex unicode escape sequence
366
writer.write(CHAR_ESCAPE);
367                         writer.write('u');
368                         // Format as \ uXXXX four digit zero padded hex string
369
writer.write(hexchar((c >> 12) & 0x0F));
370                         writer.write(hexchar((c >> 8) & 0x0F));
371                         writer.write(hexchar((c >> 4) & 0x0F));
372                         writer.write(hexchar(c & 0x0F));
373                     } else {
374                         writer.write(c);
375                     }
376                 }
377             }
378             writer.write('\"');
379             return writer.toString();
380         } catch (java.io.IOException JavaDoc e) {
381             throw new ChainedException(e);
382         }
383     }
384
385     static char hexchar (int c) {
386         char hexchars[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
387         return (hexchars[c & 0x0F]);
388     }
389 }
390
Popular Tags