blob: 47bc2ec7649e568312f74d40a84acd56bdbbc1a3 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.core.ext.linker.impl;
import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.Artifact;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.SoftPermutation;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.dev.util.Util;
import com.google.gwt.util.tools.Utility;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* A base class for Linkers that use an external script to boostrap the GWT
* module. This implementation injects JavaScript Snippets into a JS program
* defined in an external file.
*/
public abstract class SelectionScriptLinker extends AbstractLinker {
/**
* TODO(bobv): Move this class into c.g.g.core.linker when HostedModeLinker
* goes away?
*/
/**
* File name for computeScriptBase.js.
*/
protected static final String COMPUTE_SCRIPT_BASE_JS = "com/google/gwt/core/ext/linker/impl/computeScriptBaseOld.js";
/**
* The extension added to demand-loaded fragment files.
*/
protected static final String FRAGMENT_EXTENSION = ".cache.js";
/**
* A subdirectory to hold all the generated fragments.
*/
protected static final String FRAGMENT_SUBDIR = "deferredjs";
/**
* Utility class to handle insertion of permutations code.
*/
protected static PermutationsUtil permutationsUtil = new PermutationsUtil();
/**
* File name for processMetas.js.
*/
protected static final String PROCESS_METAS_JS = "com/google/gwt/core/ext/linker/impl/processMetasOld.js";
protected static void replaceAll(StringBuffer buf, String search,
String replace) {
int len = search.length();
for (int pos = buf.indexOf(search); pos >= 0; pos = buf.indexOf(search,
pos + 1)) {
buf.replace(pos, pos + len, replace);
}
}
/**
* This method is left in place for existing subclasses of
* SelectionScriptLinker that have not been upgraded for the sharding API.
*/
@Override
public ArtifactSet link(TreeLogger logger, LinkerContext context,
ArtifactSet artifacts) throws UnableToCompleteException {
ArtifactSet toReturn = link(logger, context, artifacts, true);
toReturn = link(logger, context, toReturn, false);
return toReturn;
}
@Override
public ArtifactSet link(TreeLogger logger, LinkerContext context,
ArtifactSet artifacts, boolean onePermutation)
throws UnableToCompleteException {
if (onePermutation) {
ArtifactSet toReturn = new ArtifactSet(artifacts);
/*
* Support having multiple compilation results because this method is also
* called from the legacy link method.
*/
for (CompilationResult compilation : toReturn.find(CompilationResult.class)) {
toReturn.addAll(doEmitCompilation(logger, context, compilation, artifacts));
maybeAddHostedModeFile(logger, context, toReturn, compilation);
}
return toReturn;
} else {
permutationsUtil.setupPermutationsMap(artifacts);
ArtifactSet toReturn = new ArtifactSet(artifacts);
EmittedArtifact art = emitSelectionScript(logger, context, artifacts);
if (art != null) {
toReturn.add(art);
}
maybeOutputPropertyMap(logger, context, toReturn);
maybeAddHostedModeFile(logger, context, toReturn, null);
return toReturn;
}
}
@Override
public boolean supportsDevModeInJunit(LinkerContext context) {
return (getHostedFilename() != "");
}
protected Collection<Artifact<?>> doEmitCompilation(TreeLogger logger,
LinkerContext context, CompilationResult result, ArtifactSet artifacts)
throws UnableToCompleteException {
String[] js = result.getJavaScript();
byte[][] bytes = new byte[js.length][];
bytes[0] = generatePrimaryFragment(logger, context, result, js, artifacts);
for (int i = 1; i < js.length; i++) {
bytes[i] = Util.getBytes(generateDeferredFragment(logger, context, i,
js[i]));
}
Collection<Artifact<?>> toReturn = new ArrayList<Artifact<?>>();
toReturn.add(emitBytes(logger, bytes[0], result.getStrongName()
+ getCompilationExtension(logger, context)));
for (int i = 1; i < js.length; i++) {
toReturn.add(emitBytes(logger, bytes[i], FRAGMENT_SUBDIR + File.separator
+ result.getStrongName() + File.separator + i + FRAGMENT_EXTENSION));
}
toReturn.addAll(emitSelectionInformation(result.getStrongName(), result));
return toReturn;
}
protected EmittedArtifact emitSelectionScript(TreeLogger logger,
LinkerContext context, ArtifactSet artifacts)
throws UnableToCompleteException {
/*
* Last modified is important to keep Development Mode refreses from
* clobbering Production Mode compiles. We set the timestamp on the
* Development Mode selection script to the same mod time as the module (to
* allow updates). For Production Mode, we just set it to now.
*/
long lastModified;
if (permutationsUtil.getPermutationsMap().isEmpty()) {
lastModified = context.getModuleLastModified();
} else {
lastModified = System.currentTimeMillis();
}
String ss = generateSelectionScript(logger, context, artifacts);
return emitString(logger, ss, context.getModuleName()
+ ".nocache.js", lastModified);
}
/**
* Generate a selection script. The selection information should previously
* have been scanned using {@link PermutationsUtil#setupPermutationsMap(ArtifactSet)}.
*/
protected String fillSelectionScriptTemplate(StringBuffer selectionScript,
TreeLogger logger, LinkerContext context, ArtifactSet artifacts,
CompilationResult result) throws
UnableToCompleteException {
String computeScriptBase;
String processMetas;
try {
computeScriptBase = Utility.getFileFromClassPath(COMPUTE_SCRIPT_BASE_JS);
processMetas = Utility.getFileFromClassPath(PROCESS_METAS_JS);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to read selection script template",
e);
throw new UnableToCompleteException();
}
replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBase);
replaceAll(selectionScript, "__PROCESS_METAS__", processMetas);
selectionScript = ResourceInjectionUtil.injectResources(selectionScript, artifacts);
permutationsUtil.addPermutationsJs(selectionScript, logger, context);
replaceAll(selectionScript, "__MODULE_FUNC__",
context.getModuleFunctionName());
replaceAll(selectionScript, "__MODULE_NAME__", context.getModuleName());
replaceAll(selectionScript, "__HOSTED_FILENAME__", getHostedFilename());
return selectionScript.toString();
}
/**
* @param logger a TreeLogger
* @param context a LinkerContext
* @param fragment the fragment number
*/
protected String generateDeferredFragment(TreeLogger logger,
LinkerContext context, int fragment, String js) {
return js;
}
/**
* Generate the primary fragment. The default implementation is based on
* {@link #getModulePrefix(TreeLogger, LinkerContext, String, int)} and
* {@link #getModuleSuffix(TreeLogger, LinkerContext)}.
*/
protected byte[] generatePrimaryFragment(TreeLogger logger,
LinkerContext context, CompilationResult result, String[] js,
ArtifactSet artifacts) throws UnableToCompleteException {
TextOutput to = new DefaultTextOutput(context.isOutputCompact());
to.print(generatePrimaryFragmentString(
logger, context, result, js[0], js.length, artifacts));
return Util.getBytes(to.toString());
}
protected String generatePrimaryFragmentString(TreeLogger logger,
LinkerContext context, CompilationResult result, String js, int length,
ArtifactSet artifacts)
throws UnableToCompleteException {
StringBuffer b = new StringBuffer();
String strongName = result == null ? "" : result.getStrongName();
b.append(getModulePrefix(logger, context, strongName, length));
b.append(js);
b.append(getModuleSuffix(logger, context));
return wrapPrimaryFragment(logger, context, b.toString(), artifacts, result);
}
protected String generateSelectionScript(TreeLogger logger,
LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException {
return generateSelectionScript(logger, context, artifacts, null);
}
protected String generateSelectionScript(TreeLogger logger,
LinkerContext context, ArtifactSet artifacts, CompilationResult result)
throws UnableToCompleteException {
String selectionScriptText;
StringBuffer buffer = readFileToStringBuffer(
getSelectionScriptTemplate(logger,context), logger);
selectionScriptText = fillSelectionScriptTemplate(
buffer, logger, context, artifacts, result);
selectionScriptText =
context.optimizeJavaScript(logger, selectionScriptText);
return selectionScriptText;
}
protected abstract String getCompilationExtension(TreeLogger logger,
LinkerContext context) throws UnableToCompleteException;
protected String getHostedFilename() {
return "";
}
/**
* Compute the beginning of a JavaScript file that will hold the main module
* implementation.
*/
protected abstract String getModulePrefix(TreeLogger logger,
LinkerContext context, String strongName)
throws UnableToCompleteException;
/**
* Compute the beginning of a JavaScript file that will hold the main module
* implementation. By default, calls
* {@link #getModulePrefix(TreeLogger, LinkerContext, String)}.
*
* @param strongName strong name of the module being emitted
* @param numFragments the number of fragments for this module, including the
* main fragment (fragment 0)
*/
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
String strongName, int numFragments) throws UnableToCompleteException {
return getModulePrefix(logger, context, strongName);
}
protected abstract String getModuleSuffix(TreeLogger logger,
LinkerContext context) throws UnableToCompleteException;
protected abstract String getSelectionScriptTemplate(TreeLogger logger,
LinkerContext context) throws UnableToCompleteException;
/**
* Add the Development Mode file to the artifact set.
*/
protected void maybeAddHostedModeFile(TreeLogger logger,
LinkerContext context, ArtifactSet artifacts, CompilationResult result)
throws UnableToCompleteException {
String hostedFilename = getHostedFilename();
if ("".equals(hostedFilename) || result != null) {
return;
}
try {
URL resource = SelectionScriptLinker.class.getResource(hostedFilename);
if (resource == null) {
logger.log(TreeLogger.ERROR,
"Unable to find support resource: " + hostedFilename);
throw new UnableToCompleteException();
}
final URLConnection connection = resource.openConnection();
// TODO: extract URLArtifact class?
EmittedArtifact hostedFile = new EmittedArtifact(
SelectionScriptLinker.class, hostedFilename) {
@Override
public InputStream getContents(TreeLogger logger)
throws UnableToCompleteException {
try {
return connection.getInputStream();
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to copy support resource", e);
throw new UnableToCompleteException();
}
}
@Override
public long getLastModified() {
return connection.getLastModified();
}
};
artifacts.add(hostedFile);
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to copy support resource", e);
throw new UnableToCompleteException();
}
}
protected void maybeOutputPropertyMap(TreeLogger logger,
LinkerContext context, ArtifactSet toReturn) {
return;
}
protected StringBuffer readFileToStringBuffer(String filename,
TreeLogger logger) throws UnableToCompleteException {
StringBuffer buffer;
try {
buffer = new StringBuffer(Utility.getFileFromClassPath(filename));
} catch (IOException e) {
logger.log(TreeLogger.ERROR, "Unable to read file: " + filename, e);
throw new UnableToCompleteException();
}
return buffer;
}
protected String wrapPrimaryFragment(TreeLogger logger,
LinkerContext context, String script, ArtifactSet artifacts,
CompilationResult result) throws UnableToCompleteException {
return script;
}
private List<Artifact<?>> emitSelectionInformation(String strongName,
CompilationResult result) {
List<Artifact<?>> emitted = new ArrayList<Artifact<?>>();
for (SortedMap<SelectionProperty, String> propertyMap : result.getPropertyMap()) {
TreeMap<String, String> propMap = new TreeMap<String, String>();
for (Map.Entry<SelectionProperty, String> entry : propertyMap.entrySet()) {
propMap.put(entry.getKey().getName(), entry.getValue());
}
// The soft properties may not be a subset of the existing set
for (SoftPermutation soft : result.getSoftPermutations()) {
// Make a copy we can add add more properties to
TreeMap<String, String> softMap = new TreeMap<String, String>(propMap);
// Make sure this SelectionInformation contains the soft properties
for (Map.Entry<SelectionProperty, String> entry : soft.getPropertyMap().entrySet()) {
softMap.put(entry.getKey().getName(), entry.getValue());
}
emitted.add(new SelectionInformation(strongName, soft.getId(), softMap));
}
}
return emitted;
}
}