001: /* ****************************************************************************
002: * CompilationEnvironment.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2007 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.compiler;
011:
012: import java.io.*;
013: import java.util.*;
014: import org.jdom.Element;
015: import org.apache.log4j.*;
016: import org.openlaszlo.server.LPS;
017: import org.openlaszlo.utils.ChainedException;
018: import org.openlaszlo.utils.ComparisonMap;
019: import org.openlaszlo.utils.FileUtils;
020: import org.openlaszlo.xml.internal.XMLUtils;
021:
022: /** Encapsulates all the context that script compilation needs to
023: * refer to. Instances of this class are threaded through the calls
024: * to instances of CompilerNode.
025: *
026: * Also contains utility functions for compiling to a file.
027: */
028: public class CompilationEnvironment {
029: private final Properties mProperties;
030: public static final String RUNTIME_PROPERTY = "runtime";
031: public static final String PROXIED_PROPERTY = "lzproxied";
032: public static final String DEBUG_PROPERTY = "debug";
033:
034: // matches the value of sc.Compiler.DEBUG_BACKTRACE
035: public static final String BACKTRACE_PROPERTY = "debugBacktrace";
036:
037: public static final String PROFILE_PROPERTY = "profile";
038: public static final String LINK_PROPERTY = "link";
039: public static final String CSSFILE_PROPERTY = "cssfile";
040: // Log all debug.write messages back to the server
041: public static final String LOGDEBUG_PROPERTY = "logdebug";
042: public static final String REMOTEDEBUG_PROPERTY = "remotedebug";
043: public static final String CONSOLEDEBUG_PROPERTY = "lzconsoledebug";
044: public static final String EMBEDFONTS_PROPERTY = "embedfonts";
045: public static final String SOURCELOCATOR_PROPERTY = "sourcelocators";
046:
047: // Flag used internally, to mark whether the user instantiated a <debug>
048: // tag manually. If they didn't, we need to add a call to instantiate one.
049: public static final String USER_DEBUG_WINDOW = "userdebugwindow";
050:
051: /** Cache for holding DOM tree from parsing library file */
052: public HashMap parsedLibraryCache = new HashMap();
053:
054: /** The root file being compiled. This is used to resolve
055: * relative pathnames. */
056: protected File mApplicationFile = null;
057: protected File mObjectFile = null;
058:
059: final SymbolGenerator methodNameGenerator;
060:
061: /** Output is written here.
062: */
063: private ObjectWriter mObjectWriter;
064: /** Main program output when generating a loadable
065: * library. Otherwise same as mObjectWriter */
066: private ObjectWriter mMainObjectWriter;
067:
068: private final FileResolver mFileResolver;
069:
070: private final CompilationErrorHandler mCompilerErrors = new CompilationErrorHandler();
071:
072: /** A ViewSchema object, to allow adding of user defined classes
073: * during compilation.
074: */
075: private final ViewSchema mSchema;
076:
077: /**
078: * CompilerMediaCache
079: */
080: private CompilerMediaCache mMediaCache = null;
081:
082: /** {canonical filenames} for libraries that have been imported;
083: * used to prevent recursive processing and including the same
084: * library more than once. */
085: private Set mImportedLibraryFiles = new HashSet();
086:
087: /** {canonical filenames} for loadable libraries that have been imported;
088: this is the set of all files included by loadable libraries (<import>'ed).
089: We keep this so we can issue a warning if two different loadable libraries
090: statically include the same library file.
091: */
092: private Map mLoadableImportedLibraryFiles = new HashMap();
093:
094: /** Keep track of all named resources, so we can check if a view references a defined resource. */
095: private Set mResourceNames = new HashSet();
096:
097: private final Parser mParser;
098: private Canvas mCanvas = null;
099:
100: /** Keep a list of assigned global id's, so we can warn when one
101: * is redefined */
102: // TODO: [07-18-03 hqm] We will compare all id's case
103: // insensitively (using ComparisonMap), because actionscript is
104: // not case sensitive. But in the future, we should preserve
105: // case.
106: private final Map idTable = new ComparisonMap();
107:
108: /** Holds a set of unresolved references to resources, so we can
109: check for undefined (possibly forward) references after all
110: app sources have been parsed.
111: Map contains resource-id => Element
112: */
113: private Map resourceReferences = new HashMap();
114:
115: /** Cache of the FontInfo information for each class. Computed by
116: ViewCompiler walking a class' superclass chain from the base
117: class.
118: */
119: private HashMap classFontInfoTable = new HashMap();
120: private FontInfo mDefaultFontInfo;
121:
122: /** Default text view width */
123: private static boolean mDefaultTextWidthInitialized = false;
124: private static int mDefaultTextWidth = 100;
125:
126: /** Default SWF version to compile to */
127: private String mDefaultRuntime = LPS.getRuntimeDefault();
128:
129: /** Constructs an instance.
130: * @param properties compilation properties
131: * @param resolver
132: * @param mcache
133: */
134: CompilationEnvironment(Properties properties,
135: FileResolver resolver, CompilerMediaCache mcache) {
136: // Use a local symbol generator so that we recycle method
137: // names for each new view, to keep the constant pool small.
138: this .methodNameGenerator = new SymbolGenerator("$m");
139: this .mSchema = new ViewSchema();
140: // lzc depends on the properties being shared, because it sets
141: // them after creating the environment
142: this .mProperties = properties;
143: this .mFileResolver = resolver;
144: this .mParser = new Parser();
145: this .mParser.setResolver(resolver);
146: this .mMediaCache = mcache;
147: }
148:
149: /** Copy fields from an existing CompilationEnvironment.
150: */
151: CompilationEnvironment(CompilationEnvironment srcEnv) {
152: // Need to share name generator so we don't create non-unique
153: // unique names!
154: this .methodNameGenerator = srcEnv.methodNameGenerator;
155: this .mProperties = (Properties) (srcEnv.getProperties().clone());
156: this .mFileResolver = srcEnv.getFileResolver();
157: this .mParser = new Parser();
158: this .mParser.setResolver(this .mFileResolver);
159: // Default property values
160: this .mSchema = srcEnv.getSchema();
161: this .mCanvas = srcEnv.getCanvas();
162:
163: this .mImportedLibraryFiles = new HashSet(srcEnv
164: .getImportedLibraryFiles());
165: this .mLoadableImportedLibraryFiles = srcEnv
166: .getLoadableImportedLibraryFiles();
167: this .mResourceNames = srcEnv.getResourceNames();
168: }
169:
170: /** Use this constructor for unit testing. The Compiler uses the
171: * constructor that takes a FileResolver. */
172: public CompilationEnvironment() {
173: this (new Properties(), FileResolver.DEFAULT_FILE_RESOLVER, null);
174: }
175:
176: void setApplicationFile(File file) {
177: mApplicationFile = file;
178: mCompilerErrors.setFileBase(file.getParent());
179: if (file.getParent() == null) {
180: mParser.basePathnames.add(0, "");
181: } else {
182: mParser.basePathnames.add(0, file.getParent());
183: }
184: // It appears that basePathnames is only used for error reporting.
185: // TODO: [12-26-2002 ows] Consolidate this list with the one
186: // in FileResolver.
187: mParser.basePathnames.add(LPS.getComponentsDirectory());
188: mParser.basePathnames.add(LPS.getFontDirectory());
189: mParser.basePathnames.add(LPS.getLFCDirectory());
190: }
191:
192: void setObjectFile(File file) {
193: mObjectFile = file;
194: }
195:
196: // For an app named /path/to/myapp.lzx, returns /path/to/build/myapp
197: public String getLibPrefix() {
198: File appfile = getApplicationFile();
199: String appname = appfile.getName();
200:
201: String basename = FileUtils.getBase(appname);
202:
203: String parent = appfile.getParent();
204: if (parent == null) {
205: parent = ".";
206: }
207:
208: String path = parent + "/" + "build" + "/" + basename;
209: return path;
210: }
211:
212: // For an app named /path/to/myapp.lzx, returns build/myapp
213: public String getLibPrefixRelative() {
214: File appfile = getApplicationFile();
215: String appname = appfile.getName();
216:
217: String basename = FileUtils.getBase(appname);
218:
219: String path = "build" + "/" + basename;
220: return path;
221: }
222:
223: public File getApplicationFile() {
224: return mApplicationFile;
225: }
226:
227: public File getObjectFile() {
228: return mObjectFile;
229: }
230:
231: public void setMediaCache(CompilerMediaCache cache) {
232: this .mMediaCache = cache;
233: }
234:
235: public CompilerMediaCache getMediaCache() {
236: return this .mMediaCache;
237: }
238:
239: public void setDefaultFontInfo(FontInfo fi) {
240: mDefaultFontInfo = fi;
241: }
242:
243: public FontInfo getDefaultFontInfo() {
244: return mDefaultFontInfo;
245: }
246:
247: /** Add canvas info. It is an error to call this before calling
248: * setCanvas (hand will currently result in a null reference
249: * exception). */
250: public void addClassFontInfo(String classname, FontInfo info) {
251: classFontInfoTable.put(classname, info);
252: }
253:
254: public FontInfo getClassFontInfo(String classname) {
255: FontInfo cached = (FontInfo) classFontInfoTable.get(classname);
256: return cached;
257: }
258:
259: public boolean getEmbedFonts() {
260: return this .getBooleanProperty(EMBEDFONTS_PROPERTY);
261: }
262:
263: public void setEmbedFonts(boolean embed) {
264: this .setProperty(EMBEDFONTS_PROPERTY, embed);
265: }
266:
267: public void setObjectWriter(ObjectWriter writer) {
268: assert mObjectWriter == null;
269: this .mObjectWriter = writer;
270: this .mMainObjectWriter = writer;
271: }
272:
273: public void setMainObjectWriter(ObjectWriter writer) {
274: assert mMainObjectWriter == null
275: || mMainObjectWriter == mObjectWriter;
276: this .mMainObjectWriter = writer;
277: }
278:
279: public void setScriptLimits(int recursion, int timeout) {
280: if (this .mMainObjectWriter != null) {
281: this .mMainObjectWriter.setScriptLimits(recursion, timeout);
282: }
283: }
284:
285: public ViewSchema getSchema() {
286: return mSchema;
287: }
288:
289: public static synchronized int getDefaultTextWidth() {
290: if (!mDefaultTextWidthInitialized) {
291: mDefaultTextWidthInitialized = true;
292: String dws = LPS.getProperty("compiler.defaultTextWidth",
293: "100");
294: try {
295: int dw = Integer.parseInt(dws);
296: mDefaultTextWidth = dw;
297: } catch (NumberFormatException e) {
298: Logger.getLogger(CompilationEnvironment.class).error(
299: "could not parse property value for compiler.defaultTextWidth: "
300: + dws);
301: }
302: }
303: return mDefaultTextWidth;
304: }
305:
306: public CompilationErrorHandler getErrorHandler() {
307: return mCompilerErrors;
308: }
309:
310: public void warn(CompilationError e) {
311: mCompilerErrors.addError(e);
312: }
313:
314: public void warn(Throwable e, Element element) {
315: mCompilerErrors.addError(new CompilationError(element, e));
316: }
317:
318: public void warn(String msg) {
319: warn(new CompilationError(msg));
320: }
321:
322: public void warn(String msg, Element element) {
323: warn(new CompilationError(msg, element));
324: }
325:
326: public Canvas getCanvas() {
327: return mCanvas;
328: }
329:
330: // We are compiling a canvas (whole program) not just a library
331: public boolean isCanvas() {
332: return mCanvas != null;
333: }
334:
335: public void setCanvas(Canvas canvas, String constructorScript) {
336: if (mCanvas != null)
337: throw new RuntimeException(
338: /* (non-Javadoc)
339: * @i18n.test
340: * @org-mes="canvas set twice"
341: */
342: org.openlaszlo.i18n.LaszloMessages.getMessage(
343: CompilationEnvironment.class.getName(),
344: "051018-316"));
345: mCanvas = canvas;
346: try {
347: getGenerator().setCanvas(canvas, constructorScript);
348: } catch (org.openlaszlo.sc.CompilerException e) {
349: throw new CompilationError(e);
350: }
351: }
352:
353: public void addId(String name, Element e) {
354: idTable.put(name, e);
355: }
356:
357: public Element getId(String name) {
358: return (Element) idTable.get(name);
359: }
360:
361: public void addResourceReference(String name, Element elt) {
362: resourceReferences.put(name, elt);
363: }
364:
365: public Map resourceReferences() {
366: return resourceReferences;
367: }
368:
369: /** Returns the SWF writer that compilation within this
370: * environment writes to.
371: * @return the object writer
372: */
373: ObjectWriter getGenerator() {
374: return mObjectWriter;
375: }
376:
377: /** By pointing at the main SWFWriter, this makes the resources
378: compile into the main app. We have to do this because we
379: haven't figured out a way to get Flash to attach individual
380: exported assets from a runtime loaded library into views in
381: the main app.
382: * @return the object writer
383: */
384:
385: ObjectWriter getResourceGenerator() {
386: return mMainObjectWriter;
387:
388: // Note: Returning the library's SWFWriter, as shown below,
389: // would make the compiler compile the resources into the
390: // loadable library:
391: //return mObjectWriter;
392: }
393:
394: private boolean mSnippet = false;
395:
396: /** Returns true if we're compiling a loadable library file.
397: * @return isLibrary
398: */
399: public boolean isImportLib() {
400: return mSnippet;
401: }
402:
403: public void setImportLib(boolean v) {
404: mSnippet = v;
405: }
406:
407: /** Returns the file resolver used in this environment.
408: * @return the object writer
409: */
410: FileResolver getFileResolver() {
411: return mFileResolver;
412: }
413:
414: Set getImportedLibraryFiles() {
415: return mImportedLibraryFiles;
416: }
417:
418: Map getLoadableImportedLibraryFiles() {
419: return mLoadableImportedLibraryFiles;
420: }
421:
422: Set getResourceNames() {
423: return mResourceNames;
424: }
425:
426: Parser getParser() {
427: return mParser;
428: }
429:
430: /** Returns the Properties object used in this environment.
431: * @return the properties
432: */
433: Properties getProperties() {
434: return mProperties;
435: }
436:
437: String getProperty(String name) {
438: return mProperties.getProperty(name);
439: }
440:
441: String getProperty(String name, String defval) {
442: return mProperties.getProperty(name, defval);
443: }
444:
445: void setProperty(String name, String value) {
446: mProperties.setProperty(name, value);
447: }
448:
449: /** Return target Flash version (5, 6, ...) **/
450: public String getRuntime() {
451: return getProperty(RUNTIME_PROPERTY, mDefaultRuntime);
452: }
453:
454: public String getRuntime(String defaultVersion) {
455: return getProperty(RUNTIME_PROPERTY, defaultVersion);
456: }
457:
458: public int getSWFVersionInt() {
459: String runtime = getRuntime();
460: if ("swf7".equals(runtime)) {
461: return 7;
462: } else if ("swf8".equals(runtime)) {
463: return 8;
464: } else {
465: throw new CompilationError("'" + runtime
466: + "' is not a SWF runtime");
467: }
468: }
469:
470: boolean getBooleanProperty(String name) {
471: return "true".equals(mProperties.getProperty(name));
472: }
473:
474: void setProperty(String name, boolean value) {
475: setProperty(name, value ? "true" : "false");
476: }
477:
478: /** Compiles <var>script</var> to bytecodes, and adds them to the
479: * output file.
480: * @param script a script
481: */
482: void compileScript(String script) {
483: try {
484: int size = getGenerator().addScript(script);
485: if (mCanvas != null) {
486: Element info = new Element("block");
487: info.setAttribute("size", "" + size);
488: mCanvas.addInfo(info);
489: }
490: } catch (org.openlaszlo.sc.CompilerException e) {
491: throw new CompilationError(e);
492: }
493: }
494:
495: void compileScript(String script, Element elt) {
496: try {
497: int size = getGenerator().addScript(
498: CompilerUtils.sourceLocationDirective(elt, true)
499: + script);
500: if (mCanvas != null) {
501: Element info = new Element("block");
502: info.setAttribute("pathname", Parser
503: .getSourceMessagePathname(elt));
504: info.setAttribute("lineno", ""
505: + Parser.getSourceLocation(elt, Parser.LINENO));
506: info.setAttribute("tagname", elt.getName());
507: if (elt.getAttribute("id") != null)
508: info
509: .setAttribute("id", elt
510: .getAttributeValue("id"));
511: if (elt.getAttribute("name") != null)
512: info.setAttribute("name", elt
513: .getAttributeValue("name"));
514: info.setAttribute("size", "" + size);
515: mCanvas.addInfo(info);
516: }
517: } catch (org.openlaszlo.sc.CompilerException e) {
518: throw new CompilationError(elt, e);
519: }
520: }
521:
522: /**
523: * @return a unique name in the SWF
524: */
525: String uniqueName() {
526: return mObjectWriter.createName();
527: }
528:
529: File resolve(String name, String base) throws FileNotFoundException {
530: return mFileResolver.resolve(this , name, base, false);
531: }
532:
533: File resolveLibrary(String name, String base)
534: throws FileNotFoundException {
535: return mFileResolver.resolve(this , name, base, true);
536: }
537:
538: File resolveReference(Element element, String aname)
539: throws CompilationError {
540: return resolveReference(element, aname, element.getName()
541: .equals("include"));
542: }
543:
544: /** Resolve the value of the named attribute, relative to the
545: * source location of the element.
546: */
547: File resolveReference(Element element, String aname,
548: boolean asLibrary) throws CompilationError {
549: String base = new File(Parser.getSourcePathname(element))
550: .getParent();
551: String href = XMLUtils.requireAttributeValue(element, aname);
552:
553: try {
554: return mFileResolver.resolve(this , href, base, asLibrary);
555: } catch (FileNotFoundException e) {
556: throw new CompilationError(element, e);
557: }
558: }
559:
560: /** Resolve the value of the parent node
561: */
562: File resolveParentReference(Element element)
563: throws CompilationError {
564: return new File(Parser.getSourcePathname((Element) element
565: .getParentElement()));
566: }
567:
568: /** Resolve the value of the "src" attribute, relative to the
569: * source location of the element.
570: */
571: File resolveReference(Element elt) throws CompilationError {
572: return (resolveReference(elt, "src"));
573: }
574:
575: /** If the argument is a relative URL with no host, return an URL
576: * that resolves to the same address relative to the destDir as
577: * the argument does relative to sourceDir. Otherwise return the
578: * argument unchanged. */
579: static String adjustRelativeURL(String string, File sourceDir,
580: File destDir) {
581: try {
582: java.net.URL url = new java.net.URL(string);
583: if (!url.getHost().equals("")) {
584: // It's on a different host. Don't resolve it.
585: return string;
586: }
587: if (url.getPath().startsWith("/")) {
588: // It's an absolute path. Don't resolve it.
589: return string;
590: }
591: String path;
592: try {
593: path = FileUtils.adjustRelativePath(url.getPath(),
594: FileUtils.toURLPath(sourceDir), FileUtils
595: .toURLPath(destDir));
596: } catch (FileUtils.RelativizationError e) {
597: throw new CompilationError(e);
598: }
599: if (url.getQuery() != null) {
600: path += "?" + url.getQuery();
601: }
602: return new java.net.URL(url.getProtocol(), url.getHost(),
603: url.getPort(), path).toExternalForm();
604: } catch (java.net.MalformedURLException e) {
605: return string;
606: }
607: }
608:
609: // [TODO hqm 01/06] this should be keyed off of the 'lzr' runtime
610: // arg, it should return true for lzr=dhtml
611: public boolean isDHTML() {
612: return Compiler.SCRIPT_RUNTIMES.contains(this .getRuntime());
613: }
614:
615: public boolean isSWF() {
616: return Compiler.SWF_RUNTIMES.contains(this .getRuntime());
617: }
618:
619: /** If the argument is a relative URL with no host, return an URL
620: * that resolves to the same address relative to the main source
621: * file as the argument does relative to the file that contains
622: * elt. Otherwise return the argument unchanged. */
623: String adjustRelativeURL(String string, Element elt) {
624: try {
625: File appdir = getApplicationFile().getCanonicalFile()
626: .getParentFile();
627: File localdir = new File(Parser.getSourcePathname(elt))
628: .getCanonicalFile().getParentFile();
629: if (appdir == null) {
630: appdir = new File(".").getCanonicalFile();
631: }
632: if (localdir == null) {
633: localdir = new File(".").getCanonicalFile();
634: }
635: return adjustRelativeURL(string, appdir, localdir);
636: } catch (java.io.IOException e) {
637: throw new CompilationError(elt, e);
638: }
639: }
640:
641: public boolean warnIfCannotContain(Element parentTag,
642: Element childTag) {
643: if (!mSchema.canContainElement(parentTag.getName(), childTag
644: .getName())) {
645: this .warn(
646: // TODO [2006-08-22 hqm] i18n this
647: "The tag '" + childTag.getName()
648: + "' cannot be used as a child of "
649: + parentTag.getName(), parentTag);
650: return false;
651: } else {
652: return true;
653: }
654:
655: }
656:
657: /** Check if all children are allowed to be contained in this tags */
658: public void checkValidChildContainment(Element element) {
659: for (Iterator iter = element.getChildren().iterator(); iter
660: .hasNext();) {
661: Element child = (Element) iter.next();
662: this.warnIfCannotContain(element, child);
663: }
664: }
665:
666: }
|