001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.util;
017:
018: import com.google.gwt.dev.cfg.ModuleDef;
019: import com.google.gwt.dev.cfg.Properties;
020: import com.google.gwt.dev.cfg.Property;
021: import com.google.gwt.dev.cfg.PropertyProvider;
022: import com.google.gwt.dev.cfg.Script;
023: import com.google.gwt.dev.cfg.Scripts;
024: import com.google.gwt.dev.cfg.Styles;
025: import com.google.gwt.dev.js.JsObfuscateNamer;
026: import com.google.gwt.dev.js.JsParser;
027: import com.google.gwt.dev.js.JsParserException;
028: import com.google.gwt.dev.js.JsSourceGenerationVisitor;
029: import com.google.gwt.dev.js.JsSymbolResolver;
030: import com.google.gwt.dev.js.JsVerboseNamer;
031: import com.google.gwt.dev.js.ast.JsName;
032: import com.google.gwt.dev.js.ast.JsProgram;
033: import com.google.gwt.dev.js.ast.JsScope;
034: import com.google.gwt.util.tools.Utility;
035:
036: import java.io.IOException;
037: import java.io.PrintWriter;
038: import java.io.Reader;
039: import java.io.StringReader;
040: import java.io.StringWriter;
041: import java.net.MalformedURLException;
042: import java.net.URL;
043: import java.util.HashSet;
044: import java.util.Iterator;
045: import java.util.Map;
046: import java.util.Set;
047: import java.util.TreeMap;
048: import java.util.Map.Entry;
049:
050: /**
051: * Generates the "module.nocache.html" file for use in both hosted and web mode.
052: * It is able to generate JavaScript with the knowledge of a module's settings.
053: * This class is used by {@link com.google.gwt.dev.GWTCompiler} and
054: * {@link com.google.gwt.dev.shell.GWTShellServlet}.
055: */
056: public class SelectionScriptGenerator {
057:
058: private static String cssInjector(String cssUrl) {
059: if (isRelativeURL(cssUrl)) {
060: return " if (!__gwt_stylesLoaded['"
061: + cssUrl
062: + "']) {\n"
063: + " __gwt_stylesLoaded['"
064: + cssUrl
065: + "'] = true;\n"
066: + " document.write('<link rel=\\\"stylesheet\\\" href=\\\"'+base+'"
067: + cssUrl + "\\\">');\n" + " }\n";
068: } else {
069: return " if (!__gwt_stylesLoaded['"
070: + cssUrl
071: + "']) {\n"
072: + " __gwt_stylesLoaded['"
073: + cssUrl
074: + "'] = true;\n"
075: + " document.write('<link rel=\\\"stylesheet\\\" href=\\\""
076: + cssUrl + "\\\">');\n" + " }\n";
077: }
078: }
079:
080: /**
081: * Determines whether or not the URL is relative.
082: *
083: * @param src the test url
084: * @return <code>true</code> if the URL is relative, <code>false</code> if
085: * not
086: */
087: private static boolean isRelativeURL(String src) {
088: // A straight absolute url for the same domain, server, and protocol.
089: if (src.startsWith("/")) {
090: return false;
091: }
092:
093: // If it can be parsed as a URL, then it's probably absolute.
094: try {
095: URL testUrl = new URL(src);
096:
097: // Let's guess that it is absolute (thus, not relative).
098: return false;
099: } catch (MalformedURLException e) {
100: // Do nothing, since it was a speculative parse.
101: }
102:
103: // Since none of the above matched, let's guess that it's relative.
104: return true;
105: }
106:
107: private static String literal(String lit) {
108: return "\"" + lit + "\"";
109: }
110:
111: private static void replaceAll(StringBuffer buf, String search,
112: String replace) {
113: int len = search.length();
114: for (int pos = buf.indexOf(search); pos >= 0; pos = buf
115: .indexOf(search, pos + 1)) {
116: buf.replace(pos, pos + len, replace);
117: }
118: }
119:
120: private static String scriptInjector(String scriptUrl) {
121: if (isRelativeURL(scriptUrl)) {
122: return " if (!__gwt_scriptsLoaded['"
123: + scriptUrl
124: + "']) {\n"
125: + " __gwt_scriptsLoaded['"
126: + scriptUrl
127: + "'] = true;\n"
128: + " document.write('<script language=\\\"javascript\\\" src=\\\"'+base+'"
129: + scriptUrl + "\\\"></script>');\n" + " }\n";
130: } else {
131: return " if (!__gwt_scriptsLoaded['"
132: + scriptUrl
133: + "']) {\n"
134: + " __gwt_scriptsLoaded['"
135: + scriptUrl
136: + "'] = true;\n"
137: + " document.write('<script language=\\\"javascript\\\" src=\\\""
138: + scriptUrl + "\\\"></script>');\n" + " }\n";
139: }
140: }
141:
142: private final String moduleFunction;
143: private final String moduleName;
144: private final Properties moduleProps;
145: private final Property[] orderedProps;
146:
147: /**
148: * Maps compilation strong name onto a <code>Set</code> of
149: * <code>String[]</code>. We use a <code>TreeMap</code> to produce the
150: * same generated code for the same set of compilations.
151: */
152: private final Map propertyValuesSetByStrongName = new TreeMap();
153: private final Scripts scripts;
154: private final Styles styles;
155:
156: /**
157: * A constructor for creating a selection script that will work only in hosted
158: * mode.
159: *
160: * @param moduleDef the module for which the selection script will be
161: * generated
162: */
163: public SelectionScriptGenerator(ModuleDef moduleDef) {
164: this .moduleName = moduleDef.getName();
165: this .moduleFunction = moduleDef.getFunctionName();
166: this .scripts = moduleDef.getScripts();
167: this .styles = moduleDef.getStyles();
168: this .moduleProps = moduleDef.getProperties();
169: this .orderedProps = null;
170: }
171:
172: /**
173: * A constructor for creating a selection script that will work in either
174: * hosted or web mode.
175: *
176: * @param moduleDef the module for which the selection script will be
177: * generated
178: * @param props the module's property objects, arranged in the same order in
179: * which sets of property values should be interpreted by the
180: * {@link #recordSelection(String[], String)} method
181: */
182: public SelectionScriptGenerator(ModuleDef moduleDef,
183: Property[] props) {
184: this .moduleName = moduleDef.getName();
185: this .moduleFunction = moduleName.replace('.', '_');
186: this .scripts = moduleDef.getScripts();
187: this .styles = moduleDef.getStyles();
188: this .moduleProps = moduleDef.getProperties();
189: this .orderedProps = (Property[]) props.clone();
190: }
191:
192: /**
193: * Generates a selection script based on the current settings.
194: *
195: * @return an JavaScript whose contents are the definition of a module.js file
196: */
197: public String generateSelectionScript(boolean obfuscate,
198: boolean asScript) {
199: try {
200: String rawSource;
201: {
202: StringWriter sw = new StringWriter();
203: PrintWriter pw = new PrintWriter(sw, true);
204:
205: String template = Utility
206: .getFileFromClassPath(asScript ? "com/google/gwt/dev/util/SelectionScriptTemplate-xs.js"
207: : "com/google/gwt/dev/util/SelectionScriptTemplate.js");
208: genScript(pw, template);
209:
210: pw.close();
211: rawSource = sw.toString();
212: }
213:
214: {
215: JsParser parser = new JsParser();
216: Reader r = new StringReader(rawSource);
217: JsProgram jsProgram = new JsProgram();
218: JsScope topScope = jsProgram.getScope();
219: JsName funcName = topScope.declareName(moduleFunction);
220: funcName.setObfuscatable(false);
221:
222: parser.parseInto(topScope, jsProgram.getGlobalBlock(),
223: r, 1);
224: JsSymbolResolver.exec(jsProgram);
225: if (obfuscate) {
226: JsObfuscateNamer.exec(jsProgram);
227: } else {
228: JsVerboseNamer.exec(jsProgram);
229: }
230:
231: DefaultTextOutput out = new DefaultTextOutput(obfuscate);
232: JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(
233: out);
234: v.accept(jsProgram);
235: return out.toString();
236: }
237: } catch (IOException e) {
238: throw new RuntimeException(
239: "Error processing selection script template.", e);
240: } catch (JsParserException e) {
241: throw new RuntimeException(
242: "Error processing selection script template.", e);
243: }
244: }
245:
246: /**
247: * Records a mapping from a unique set of client property values onto a strong
248: * name (that is, a compilation).
249: *
250: * @param values a set of client property values ordered such that the i'th
251: * value corresponds with the i'th property in {@link #props}
252: * @param strongName the base name of a compiled <code>.cache.html</code>
253: * file
254: */
255: public void recordSelection(String[] values, String strongName) {
256: Set valuesSet = (Set) propertyValuesSetByStrongName
257: .get(strongName);
258: if (valuesSet == null) {
259: valuesSet = new HashSet();
260: propertyValuesSetByStrongName.put(strongName, valuesSet);
261: }
262: valuesSet.add(values.clone());
263: }
264:
265: private void genAnswers(PrintWriter pw) {
266: for (Iterator iter = propertyValuesSetByStrongName.entrySet()
267: .iterator(); iter.hasNext();) {
268: Map.Entry entry = (Entry) iter.next();
269: String strongName = (String) entry.getKey();
270: Set propValuesSet = (Set) entry.getValue();
271:
272: // Create one answers entry for each string array in the set.
273: //
274: for (Iterator iterator = propValuesSet.iterator(); iterator
275: .hasNext();) {
276: String[] propValues = (String[]) iterator.next();
277:
278: pw.print(" unflattenKeylistIntoAnswers([");
279: boolean firstPrint = true;
280: for (int i = 0; i < orderedProps.length; i++) {
281: Property prop = orderedProps[i];
282: String activeValue = prop.getActiveValue();
283: if (activeValue == null) {
284: // This is a call to a property provider function; we need it to
285: // select the script.
286: //
287: if (!firstPrint) {
288: pw.print(",");
289: }
290: firstPrint = false;
291: pw.print(literal(propValues[i]));
292: } else {
293: // This property was explicitly set at compile-time; we do not need
294: // it.
295: }
296: }
297: pw.print("]");
298: pw.print(",");
299: pw.print(literal(strongName));
300: pw.println(");");
301: }
302: }
303: }
304:
305: private void genPropProviders(PrintWriter pw) {
306: for (Iterator iter = moduleProps.iterator(); iter.hasNext();) {
307: Property prop = (Property) iter.next();
308: String activeValue = prop.getActiveValue();
309: if (activeValue == null) {
310: // Emit a provider function, defined by the user in module config.
311: PropertyProvider provider = prop.getProvider();
312: assert (provider != null) : "expecting a default property provider to have been set";
313: String js = provider.getBody().toSource();
314: pw.print("providers['" + prop.getName()
315: + "'] = function() ");
316: pw.print(js);
317: pw.println(";");
318:
319: // Emit a map of allowed property values as an object literal.
320: pw.println();
321: pw.println("values['" + prop.getName() + "'] = {");
322: String[] knownValues = prop.getKnownValues();
323: for (int i = 0; i < knownValues.length; i++) {
324: if (i > 0) {
325: pw.println(", ");
326: }
327: // Each entry is of the form: "propName":<index>.
328: // Note that we depend here on the known values being already
329: // enclosed in quotes (because property names can have dots which
330: // aren't allowed unquoted as keys in the object literal).
331: pw.print(literal(knownValues[i]) + ": ");
332: pw.print(i);
333: }
334: pw.println();
335: pw.println("};");
336: }
337: }
338: }
339:
340: private void genPropValues(PrintWriter pw) {
341: for (int i = 0; i < orderedProps.length; i++) {
342: Property prop = orderedProps[i];
343: String activeValue = prop.getActiveValue();
344: if (activeValue == null) {
345: // This is a call to a property provider function; we need it to
346: // select the script.
347: //
348: PropertyProvider provider = prop.getProvider();
349: assert (provider != null) : "expecting a default property provider to have been set";
350: // When we call the provider, we supply a bogus argument to indicate
351: // that it should throw an exception if the property is a bad value.
352: // The absence of arguments (as in hosted mode) tells it to return null.
353: pw.print("[");
354: pw.print("computePropValue('" + prop.getName() + "')");
355: pw.print("]");
356: } else {
357: // This property was explicitly set at compile-time; we do not need it.
358: }
359: }
360: }
361:
362: /**
363: * Emits all the script required to set up the module and, in web mode, select
364: * a compilation.
365: *
366: * @param pw
367: */
368: private void genScript(PrintWriter mainPw, String template) {
369: StringBuffer buf = new StringBuffer(template);
370: replaceAll(buf, "__MODULE_FUNC__", moduleFunction);
371: replaceAll(buf, "__MODULE_NAME__", moduleName);
372:
373: if (orderedProps != null) {
374: // Remove shell servlet only stuff (hosted mode support)
375: int startPos = buf
376: .indexOf("// __SHELL_SERVLET_ONLY_BEGIN__");
377: int endPos = buf.indexOf("// __SHELL_SERVLET_ONLY_END__");
378: buf.delete(startPos, endPos);
379: }
380:
381: // Add external dependencies
382: int startPos = buf.indexOf("// __MODULE_DEPS_END__");
383: for (Iterator iter = styles.iterator(); iter.hasNext();) {
384: String style = (String) iter.next();
385: String text = cssInjector(style);
386: buf.insert(startPos, text);
387: startPos += text.length();
388: }
389:
390: for (Iterator iter = scripts.iterator(); iter.hasNext();) {
391: Script script = (Script) iter.next();
392: String text = scriptInjector(script.getSrc());
393: buf.insert(startPos, text);
394: startPos += text.length();
395: }
396:
397: // Add property providers
398: {
399: StringWriter sw = new StringWriter();
400: PrintWriter pw = new PrintWriter(sw, true);
401: genPropProviders(pw);
402: pw.close();
403: String stuff = sw.toString();
404: startPos = buf.indexOf("// __PROPERTIES_END__");
405: buf.insert(startPos, stuff);
406: }
407:
408: // Add permutations
409: {
410: StringWriter sw = new StringWriter();
411: PrintWriter pw = new PrintWriter(sw, true);
412:
413: // If the ordered props are specified, then we're generating for both
414: // modes.
415: if (orderedProps != null) {
416: // Determine if there's only one possible answer.
417: if (propertyValuesSetByStrongName.size() > 1) {
418: // Multiple answers; generate computations.
419: pw.println();
420: genAnswers(pw);
421: pw.println();
422: pw.print(" strongName = answers");
423: genPropValues(pw);
424: } else {
425: // Only one answer; explicit properties set or rare cases.
426: Set entrySet = propertyValuesSetByStrongName
427: .entrySet();
428: assert (entrySet.size() == 1);
429: Map.Entry entry = (Entry) entrySet.iterator()
430: .next();
431: String strongName = (String) entry.getKey();
432: // Just use a literal for the single answer.
433: pw.print(" strongName = " + literal(strongName));
434: }
435: pw.println(";");
436: }
437:
438: pw.close();
439: String stuff = sw.toString();
440: startPos = buf.indexOf("// __PERMUTATIONS_END__");
441: buf.insert(startPos, stuff);
442: }
443:
444: mainPw.print(buf.toString());
445: }
446:
447: }
|