001: /*****************************************************************************
002: * ObjectWriter.java
003: * ****************************************************************************/package org.openlaszlo.compiler;
004:
005: import org.openlaszlo.sc.ScriptCompiler;
006: import org.openlaszlo.server.LPS;
007: import org.openlaszlo.utils.ChainedException;
008: import org.openlaszlo.utils.FileUtils;
009: import org.openlaszlo.utils.ListFormat;
010: import org.openlaszlo.iv.flash.api.*;
011: import org.openlaszlo.iv.flash.api.action.*;
012: import org.openlaszlo.iv.flash.api.button.*;
013: import org.openlaszlo.iv.flash.api.image.*;
014: import org.openlaszlo.iv.flash.api.sound.*;
015: import org.openlaszlo.iv.flash.api.shape.*;
016: import org.openlaszlo.iv.flash.api.text.*;
017: import org.openlaszlo.iv.flash.util.*;
018: import org.openlaszlo.iv.flash.cache.*;
019: import org.openlaszlo.compiler.CompilationEnvironment;
020:
021: import org.openlaszlo.media.*;
022:
023: import java.io.*;
024: import java.util.*;
025: import java.lang.Math;
026: import java.lang.Character;
027:
028: import org.jdom.Element;
029:
030: // jgen 1.4
031: import java.awt.geom.Rectangle2D;
032:
033: import org.apache.log4j.*;
034:
035: /** Accumulates code, XML, and assets to an object file.
036: *
037: * Properties documented in Compiler.getProperties.
038: */
039: abstract class ObjectWriter {
040: /** Stream to write to. */
041: protected OutputStream mStream = null;
042:
043: /** True iff close() has been called. */
044: protected boolean mCloseCalled = false;
045:
046: FontsCollector mFontsCollector = new FontsCollector();
047:
048: /** InfoXML */
049: // [todo hqm 2006-08-02] change the name of this to something more
050: // generic than 'swf'
051: protected Element mInfo = new Element("swf");
052:
053: protected final CompilationEnvironment mEnv;
054:
055: /** Set of resoures we're importing into the output */
056: protected final HashSet mMultiFrameResourceSet = new HashSet();
057:
058: /** Unique name supply for clip/js names */
059: protected SymbolGenerator mNameSupply = new SymbolGenerator("$LZ");
060:
061: /** Has the preloader been added? */
062: protected boolean mPreloaderAdded = false;
063:
064: /** Constant */
065: protected final int TWIP = 20;
066:
067: /** Properties */
068: protected Properties mProperties;
069:
070: protected String liburl = "";
071:
072: /** media cache for transcoding */
073: protected CompilerMediaCache mCache = null;
074:
075: /** <String,Resource> maps resource files to the Resources
076: * definition in the swf file. */
077: protected Map mResourceMap = new HashMap();
078: protected Map mClickResourceMap = new HashMap();
079: protected Map mMultiFrameResourceMap = new HashMap();
080:
081: /** Logger */
082: protected static Logger mLogger = org.apache.log4j.Logger
083: .getLogger(ObjectWriter.class);
084:
085: /** Canvas Height */
086: protected int mHeight = 0;
087:
088: /** Canvas Width */
089: protected int mWidth = 0;
090:
091: protected int mRecursionLimit = 0;
092: protected int mExecutionTimeout = 0;
093:
094: /**
095: * Initialize jgenerator
096: */
097: static {
098: org.openlaszlo.iv.flash.util.Util
099: .init(LPS.getConfigDirectory());
100: }
101:
102: /** Logger for jgenerator */
103: /**
104: * Initializes a ObjectWriter with an OutputStream to which a new object file
105: * will be written when <code>ObjectWriter.close()</code> is called.
106: *
107: * @param stream A <code>java.io.OutputStream</code> that the
108: * movie will be written to.
109: * @param props list of properties
110: * @param cache media cache
111: * @param importLibrary If true, the compiler will add in the LaszloLibrary.
112: */
113: ObjectWriter(Properties props, OutputStream stream,
114: CompilerMediaCache cache, boolean importLibrary,
115: CompilationEnvironment env) {
116: this .mProperties = props;
117: this .mCache = cache;
118: this .mStream = stream;
119: this .mEnv = env;
120: }
121:
122: /**
123: * Sets the canvas for the app
124: *
125: * @param canvas
126: *
127: */
128: abstract void setCanvas(Canvas canvas, String canvasConstructor);
129:
130: abstract void setCanvasDefaults(Canvas canvas, CompilerMediaCache mc);
131:
132: /** Compiles the specified script to bytecodes
133: * and add its bytecodes to the app.
134: *
135: * @param script the script to be compiled
136: * @return the number of bytes
137: */
138: public abstract int addScript(String script);
139:
140: class ImportResourceError extends CompilationError {
141:
142: ImportResourceError(String fileName, CompilationEnvironment env) {
143: super ("Can't import " + stripBaseName(fileName, env));
144: }
145:
146: ImportResourceError(String fileName, String type,
147: CompilationEnvironment env) {
148: super ("Can't import " + type + " "
149: + stripBaseName(fileName, env));
150: }
151:
152: ImportResourceError(String fileName, Exception e,
153: CompilationEnvironment env) {
154: super ("Can't import " + stripBaseName(fileName, env) + ": "
155: + e.getMessage());
156: }
157:
158: ImportResourceError(String fileName, Exception e, String type,
159: CompilationEnvironment env) {
160: super ("Can't import " + type + " "
161: + stripBaseName(fileName, env) + ": "
162: + e.getMessage());
163: }
164:
165: }
166:
167: public static String stripBaseName(String fileName,
168: CompilationEnvironment env) {
169: try {
170: fileName = (new File(fileName)).getCanonicalFile()
171: .toString();
172: } catch (java.io.IOException e) {
173: }
174: String base = env.getErrorHandler().fileBase;
175: if (base == null || "".equals(base)) {
176: return fileName;
177: }
178: base = (new File(base)).getAbsolutePath() + File.separator;
179: if (base != null) {
180: int i = 1;
181: // Find longest common substring
182: while (i < base.length()
183: && fileName.startsWith(base.substring(0, i))) {
184: i++;
185: }
186: // remove base string prefix
187: return fileName.substring(i - 1);
188: } else {
189: return fileName;
190: }
191: }
192:
193: /** Find a resource for importing into a movie and return a flashdef.
194: * Includes stop action.
195: *
196: * @param fileName file name of the resource
197: * @param name name of the resource
198: */
199: protected Resource getResource(String fileName, String name)
200: throws ImportResourceError {
201: return getResource(fileName, name, true);
202: }
203:
204: /** Find a resource for importing into a movie and return a flashdef.
205: *
206: * @param name name of the resource
207: * @param fileName file name of the resource
208: * @param stop include stop action if true
209: */
210: protected Resource getResource(String fileName, String name,
211: boolean stop) throws ImportResourceError {
212: try {
213: String inputMimeType = MimeType.fromExtension(fileName);
214: if (!Transcoder.canTranscode(inputMimeType, MimeType.SWF)
215: && !inputMimeType.equals(MimeType.SWF)) {
216: inputMimeType = Transcoder
217: .guessSupportedMimeTypeFromContent(fileName);
218: if (inputMimeType == null || inputMimeType.equals("")) {
219: throw new ImportResourceError(fileName,
220: new Exception(
221: /* (non-Javadoc)
222: * @i18n.test
223: * @org-mes="bad mime type"
224: */
225: org.openlaszlo.i18n.LaszloMessages
226: .getMessage(ObjectWriter.class
227: .getName(), "051018-549")),
228: mEnv);
229: }
230: }
231: // No need to get these from the cache since they don't need to be
232: // transcoded and we usually keep the cmcache on disk.
233: if (inputMimeType.equals(MimeType.SWF)) {
234:
235: long fileSize = FileUtils.getSize(new File(fileName));
236:
237: Element elt = new Element("resource");
238: elt.setAttribute("name", name);
239: elt.setAttribute("mime-type", inputMimeType);
240: elt.setAttribute("source", fileName);
241: elt.setAttribute("filesize", "" + fileSize);
242: mInfo.addContent(elt);
243:
244: return importSWF(fileName, name, false);
245: }
246:
247: // TODO: [2002-12-3 bloch] use cache for mp3s; for now we're skipping it
248: // arguably, this is a fixme
249: if (inputMimeType.equals(MimeType.MP3)
250: || inputMimeType.equals(MimeType.XMP3)) {
251: return importMP3(fileName, name);
252: }
253:
254: File inputFile = new File(fileName);
255: File outputFile = mCache.transcode(inputFile,
256: inputMimeType, MimeType.SWF);
257: mLogger.debug(
258: /* (non-Javadoc)
259: * @i18n.test
260: * @org-mes="importing: " + p[0] + " as " + p[1] + " from cache; size: " + p[2]
261: */
262: org.openlaszlo.i18n.LaszloMessages.getMessage(
263: ObjectWriter.class.getName(), "051018-584",
264: new Object[] { fileName, name,
265: new Long(outputFile.length()) }));
266:
267: long fileSize = FileUtils.getSize(outputFile);
268:
269: Element elt = new Element("resource");
270: elt.setAttribute("name", name);
271: elt.setAttribute("mime-type", inputMimeType);
272: elt.setAttribute("source", fileName);
273: elt.setAttribute("filesize", "" + fileSize);
274: mInfo.addContent(elt);
275:
276: return importSWF(outputFile.getPath(), name, stop);
277: } catch (Exception e) {
278: mLogger.error(
279: /* (non-Javadoc)
280: * @i18n.test
281: * @org-mes="Can't get resource " + p[0]
282: */
283: org.openlaszlo.i18n.LaszloMessages.getMessage(
284: ObjectWriter.class.getName(), "051018-604",
285: new Object[] { fileName }));
286: throw new ImportResourceError(fileName, e, mEnv);
287: }
288:
289: }
290:
291: abstract void addPreloaderScript(String script);
292:
293: abstract void addPreloader(CompilationEnvironment env);
294:
295: /** Import a resource file into the preloader movie.
296: * Using a name that already exists clobbers the
297: * old resource (for now).
298: *
299: * @param fileName file name of the resource
300: * @param name name of the MovieClip/Sprite
301: * @throws CompilationError
302: */
303: abstract public void importPreloadResource(String fileName,
304: String name) throws ImportResourceError;
305:
306: abstract public void importPreloadResource(File fileName,
307: String name) throws ImportResourceError;
308:
309: /** Import a multiframe resource into the current movie. Using a
310: * name that already exists clobbers the old resource (for now).
311: */
312: abstract public void importPreloadResource(List sources,
313: String name, File parent) throws ImportResourceError;
314:
315: /** Imports file, if it has not previously been imported, and
316: * returns in any case the name of the clip that refers to it.
317: * File should refer to a graphical asset. */
318: public String importResource(File file) {
319: mLogger.debug("ObjectResource:importResource(File) "
320: + file.getPath());
321: Resource res;
322:
323: try {
324: file = file.getCanonicalFile();
325: res = (Resource) mResourceMap.get(file.getPath());
326: } catch (java.io.IOException e) {
327: throw new CompilationError(e);
328: }
329: if (res == null) {
330: String clipName = createName();
331: importResource(file.getPath(), clipName);
332: return clipName;
333: } else {
334: return res.getName();
335: }
336: }
337:
338: /** Import a resource file into the current movie.
339: * Using a name that already exists clobbers the
340: * old resource (for now).
341: *
342: * @param fileName file name of the resource
343: * @param name name of the MovieClip/Sprite
344: * @throws CompilationError
345: */
346: abstract public void importResource(String fileName, String name)
347: throws ImportResourceError;
348:
349: abstract public void importResource(File fFile, String name)
350: throws ImportResourceError;
351:
352: abstract public void importResource(List sources, String name,
353: File parent);
354:
355: /** Returns a new unique js name. */
356: String createName() {
357: return mNameSupply.next();
358: }
359:
360: /**
361: * collect fonts for later use
362: */
363: private void collectFonts(Script s) {
364:
365: Timeline tl = s.getTimeline();
366: // If preloader is added, skip frame 0. Assume that preloader is only
367: // one frame long starting at frame 0.
368: for (int i = 0; i < tl.getFrameCount(); i++) {
369: Frame frame = tl.getFrameAt(i);
370: for (int k = 0; k < frame.size(); k++) {
371: FlashObject fo = frame.getFlashObjectAt(k);
372: fo.collectFonts(mFontsCollector);
373: //mLogger.debug("FONTS size "
374: //+ mFontsCollector.getFonts().size());
375: }
376: }
377: }
378:
379: /**
380: * @param fileName
381: * @param name
382: * @param addStop if true, add stop action to last frame
383: */
384: protected Resource importSWF(String fileName, String name,
385: boolean addStop) throws IVException, FileNotFoundException {
386:
387: FlashFile f = FlashFile.parse(fileName);
388: Script s = f.getMainScript();
389: collectFonts(s);
390: if (addStop) {
391: Frame frame = s.getFrameAt(s.getFrameCount() - 1);
392: frame.addStopAction();
393: }
394:
395: Rectangle2D rect = s.getBounds();
396: int mw = (int) (rect.getMaxX() / TWIP);
397: int mh = (int) (rect.getMaxY() / TWIP);
398:
399: Resource res = new Resource(name, s, mw, mh);
400:
401: // Add multi-frame SWF resources that have bounds that
402: // are different than their first frame to the resource table.
403: if (s.getFrameCount() > 1) {
404:
405: Rectangle2D f1Rect = new Rectangle2D.Double();
406: s.getFrameAt(0).addBounds(f1Rect);
407: int f1w = (int) (f1Rect.getMaxX() / TWIP);
408: int f1h = (int) (f1Rect.getMaxY() / TWIP);
409: if (f1w < mw || f1h < mh) {
410: mMultiFrameResourceSet.add(res);
411: }
412: }
413:
414: return res;
415: }
416:
417: /**
418: * @param fileName
419: * @param name
420: */
421: protected Resource importMP3(String fileName, String name)
422: throws IVException, IOException {
423:
424: long fileSize = FileUtils.getSize(new File(fileName));
425: FileInputStream stream = new FileInputStream(fileName);
426: FlashBuffer buffer = new FlashBuffer(stream);
427: Sound sound = MP3Sound.newMP3Sound(buffer);
428:
429: Element elt = new Element("resource");
430: elt.setAttribute("name", name);
431: elt.setAttribute("mime-type", MimeType.MP3);
432: elt.setAttribute("source", fileName);
433: elt.setAttribute("filesize", "" + fileSize);
434: mInfo.addContent(elt);
435:
436: stream.close();
437:
438: return new Resource(sound);
439: }
440:
441: /** Writes the object code to the <code>OutputStream</code> that was
442: * supplied to the ObjectWriter's constructor.
443: * @throws IOException if an error occurs
444: */
445: abstract public void close() throws IOException;
446:
447: abstract public void openSnippet(String url) throws IOException;
448:
449: abstract public void closeSnippet() throws IOException;
450:
451: /**
452: * Generate a warning message
453: */
454: void warn(CompilationEnvironment env, String msg) {
455: CompilationError cerr;
456: env.warn(msg);
457: }
458:
459: /**
460: * A resource we've imported
461: */
462: class Resource implements Comparable {
463: /** Name of the resource */
464: protected String mName = "";
465: /** width of the resource in pixels */
466: protected int mWidth = 0;
467: /** height of the resource in pixels */
468: protected int mHeight = 0;
469: /** Flash definition of this resource */
470: protected FlashDef mFlashDef = null;
471:
472: /** Create a resource that represents this flash def
473: * @param def
474: */
475: public Resource(FlashDef def) {
476: mFlashDef = def;
477: }
478:
479: /** Create a resource
480: */
481: public Resource(String name, FlashDef def, int width, int height) {
482: mName = name;
483: mFlashDef = def;
484: mWidth = width;
485: mHeight = height;
486: }
487:
488: public Resource(String name, int width, int height) {
489: mName = name;
490: mWidth = width;
491: mHeight = height;
492: }
493:
494: public String getName() {
495: return mName;
496: }
497:
498: public int getWidth() {
499: return mWidth;
500: }
501:
502: public int getHeight() {
503: return mHeight;
504: }
505:
506: public FlashDef getFlashDef() {
507: return mFlashDef;
508: }
509:
510: public int compareTo(Object other) throws ClassCastException {
511: Resource o = (Resource) other;
512: return mName.compareTo(o.mName);
513: }
514: }
515:
516: abstract public void importBaseLibrary(String library,
517: CompilationEnvironment env);
518:
519: abstract public String importClickResource(File file)
520: throws ImportResourceError;
521:
522: /** Find a resource for importing into a movie and return a flashdef.
523: * Doesn't includes stop action.
524: *
525: * @param fileName file name of the resource
526: * @param name name of the resource
527: */
528: protected Resource getMultiFrameResource(String fileName,
529: String name, int fNum) throws ImportResourceError {
530: Resource res = (Resource) mMultiFrameResourceMap.get(fileName);
531: if (res != null) {
532: return res;
533: }
534:
535: res = getResource(fileName, "$" + name + "_lzf_" + (fNum + 1),
536: false);
537:
538: mMultiFrameResourceMap.put(fileName, res);
539: return res;
540: }
541:
542: /* [todo 2006-02-09 hqm] These methods are to be compatible with
543: SWF font machinery -- this should get factored away someday so that the FontCompiler
544: doesn't try to do anything with <font> tags in DHTML, (except maybe make aliases for them?)
545: */
546: abstract FontManager getFontManager();
547:
548: abstract public boolean isDeviceFont(String face);
549:
550: abstract public void setDeviceFont(String face);
551:
552: abstract public void setFontManager(FontManager fm);
553:
554: abstract void importFontStyle(String fileName, String face,
555: String style, CompilationEnvironment env)
556: throws FileNotFoundException, CompilationError;
557:
558: public void setScriptLimits(int recursion, int timeout) {
559: this.mRecursionLimit = recursion;
560: this.mExecutionTimeout = timeout;
561: }
562:
563: }
|