/*
* 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.dev;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.javac.CompilationState;
import com.google.gwt.dev.javac.UnitCacheSingleton;
import com.google.gwt.dev.shell.ArtifactAcceptor;
import com.google.gwt.dev.shell.BrowserChannelServer;
import com.google.gwt.dev.shell.BrowserListener;
import com.google.gwt.dev.shell.BrowserWidgetHost;
import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
import com.google.gwt.dev.shell.CheckForUpdates;
import com.google.gwt.dev.shell.ModuleSpaceHost;
import com.google.gwt.dev.shell.OophmSessionHandler;
import com.google.gwt.dev.shell.ShellModuleSpaceHost;
import com.google.gwt.dev.shell.remoteui.RemoteUI;
import com.google.gwt.dev.ui.DevModeUI;
import com.google.gwt.dev.ui.DoneCallback;
import com.google.gwt.dev.ui.DoneEvent;
import com.google.gwt.dev.util.BrowserInfo;
import com.google.gwt.dev.util.arg.ArgHandlerEnableGeneratorResultCaching;
import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
import java.io.File;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Semaphore;
/**
* The main executable class for the hosted mode shell. This class must not have
* any GUI dependencies.
*/
public abstract class DevModeBase implements DoneCallback {
/**
* Implementation of BrowserWidgetHost that supports the abstract UI
* interface.
*/
public class UiBrowserWidgetHostImpl implements BrowserWidgetHost {
@Override
public ModuleHandle createModuleLogger(String moduleName, String userAgent, String url,
String tabKey, String sessionKey, BrowserChannelServer serverChannel, byte[] userAgentIcon) {
if (sessionKey == null) {
// if we don't have a unique session key, make one up
sessionKey = randomString();
}
TreeLogger.Type maxLevel = options.getLogLevel();
String agentTag = BrowserInfo.getShortName(userAgent);
String remoteSocket = serverChannel.getRemoteEndpoint();
ModuleHandle module =
ui.getModuleLogger(userAgent, remoteSocket, url, tabKey, moduleName, sessionKey,
agentTag, userAgentIcon, maxLevel);
return module;
}
@Override
public ModuleSpaceHost createModuleSpaceHost(ModuleHandle module, String moduleName)
throws UnableToCompleteException {
Event moduleSpaceHostCreateEvent =
SpeedTracerLogger.start(DevModeEventType.MODULE_SPACE_HOST_CREATE, "Module Name",
moduleName);
// TODO(jat): add support for closing an active module
TreeLogger logger = module.getLogger();
try {
// Try to find an existing loaded version of the module def.
ModuleDef moduleDef = loadModule(logger, moduleName, true);
assert (moduleDef != null);
ArchivePreloader.preloadArchives(logger, compilerContext);
CompilationState compilationState = moduleDef.getCompilationState(logger, compilerContext);
ShellModuleSpaceHost host =
doCreateShellModuleSpaceHost(logger, compilationState, moduleDef);
return host;
} catch (RuntimeException e) {
logger.log(TreeLogger.ERROR, "Exception initializing module", e);
module.unload();
throw e;
} finally {
moduleSpaceHostCreateEvent.end();
}
}
}
/**
* Handles the -bindAddress command line flag.
*/
protected static class ArgHandlerBindAddress extends ArgHandlerString {
private static final String BIND_ADDRESS_TAG = "-bindAddress";
private static final String DEFAULT_BIND_ADDRESS = "127.0.0.1";
private final OptionBindAddress options;
public ArgHandlerBindAddress(OptionBindAddress options) {
this.options = options;
}
@Override
public String[] getDefaultArgs() {
return new String[]{BIND_ADDRESS_TAG, DEFAULT_BIND_ADDRESS};
}
@Override
public String getPurpose() {
return "Specifies the bind address for the code server and web server " + "(defaults to "
+ DEFAULT_BIND_ADDRESS + ")";
}
@Override
public String getTag() {
return BIND_ADDRESS_TAG;
}
@Override
public String[] getTagArgs() {
return new String[]{"host-name-or-address"};
}
@Override
public boolean setString(String value) {
try {
InetAddress address = InetAddress.getByName(value);
options.setBindAddress(value);
if (address.isAnyLocalAddress()) {
// replace a wildcard address with our machine's local address
// this isn't fully accurate, as there is no guarantee we will get
// the right one on a multihomed host
options.setConnectAddress(InetAddress.getLocalHost().getHostAddress());
} else {
options.setConnectAddress(value);
}
return true;
} catch (UnknownHostException e) {
System.err.println("-bindAddress host \"" + value + "\" unknown");
return false;
}
}
}
/**
* Handles the -blacklist command line argument.
*/
protected static class ArgHandlerBlacklist extends ArgHandlerString {
public ArgHandlerBlacklist() {
}
@Override
public String getPurpose() {
return "Prevents the user browsing URLs that match the specified regexes (comma or space separated)";
}
@Override
public String getTag() {
return "-blacklist";
}
@Override
public String[] getTagArgs() {
return new String[]{"blacklist-string"};
}
@Override
public boolean setString(String blacklistStr) {
return BrowserWidgetHostChecker.blacklistRegexes(blacklistStr);
}
}
/**
* Handles the -codeServerPort command line flag.
*/
protected static class ArgHandlerCodeServerPort extends ArgHandlerString {
private static final String CODE_SERVER_PORT_TAG = "-codeServerPort";
private static final String DEFAULT_PORT = "9997";
private final OptionCodeServerPort options;
public ArgHandlerCodeServerPort(OptionCodeServerPort options) {
this.options = options;
}
@Override
public String[] getDefaultArgs() {
return new String[]{CODE_SERVER_PORT_TAG, DEFAULT_PORT};
}
@Override
public String getPurpose() {
return "Specifies the TCP port for the code server (defaults to " + DEFAULT_PORT + ")";
}
@Override
public String getTag() {
return CODE_SERVER_PORT_TAG;
}
@Override
public String[] getTagArgs() {
return new String[]{"port-number | \"auto\""};
}
@Override
public boolean setString(String value) {
if (value.equals("auto")) {
options.setCodeServerPort(0);
} else {
try {
options.setCodeServerPort(Integer.parseInt(value));
} catch (NumberFormatException e) {
System.err.println("A port must be an integer or \"auto\"");
return false;
}
}
return true;
}
}
/**
* Handles the -logdir command line option.
*/
protected static class ArgHandlerLogDir extends ArgHandlerString {
private final OptionLogDir options;
public ArgHandlerLogDir(OptionLogDir options) {
this.options = options;
}
@Override
public String getPurpose() {
return "Logs to a file in the given directory, as well as graphically";
}
@Override
public String getTag() {
return "-logdir";
}
@Override
public String[] getTagArgs() {
return new String[]{"directory"};
}
@Override
public boolean setString(String value) {
options.setLogFile(value);
return true;
}
}
/**
* Runs a convenient embedded web server.
*/
protected static class ArgHandlerNoServerFlag extends ArgHandlerFlag {
private final OptionNoServer options;
public ArgHandlerNoServerFlag(OptionNoServer options) {
this.options = options;
addTagValue("-noserver", false);
}
@Override
public String getPurposeSnippet() {
return "Starts a servlet container serving the directory specified by the -war flag.";
}
@Override
public String getLabel() {
return "startServer";
}
@Override
public boolean setFlag(boolean value) {
options.setNoServer(!value);
return true;
}
@Override
public boolean getDefaultValue() {
return !options.isNoServer();
}
}
/**
* Handles the -port command line flag.
*/
protected static class ArgHandlerPort extends ArgHandlerString {
private final OptionPort options;
public ArgHandlerPort(OptionPort options) {
this.options = options;
}
@Override
public String[] getDefaultArgs() {
return new String[]{getTag(), "8888"};
}
@Override
public String getPurpose() {
return "Specifies the TCP port for the embedded web server (defaults to 8888)";
}
@Override
public String getTag() {
return "-port";
}
@Override
public String[] getTagArgs() {
return new String[]{"port-number | \"auto\""};
}
@Override
public boolean setString(String value) {
if (value.equals("auto")) {
options.setPort(0);
} else {
try {
options.setPort(Integer.parseInt(value));
} catch (NumberFormatException e) {
System.err.println("A port must be an integer or \"auto\"");
return false;
}
}
return true;
}
}
/**
* Handles the -remoteUI command line flag.
*/
protected static class ArgHandlerRemoteUI extends ArgHandlerString {
private final HostedModeBaseOptions options;
public ArgHandlerRemoteUI(HostedModeBaseOptions options) {
this.options = options;
}
@Override
public String getPurpose() {
return "Sends Development Mode UI event information to the specified host and port.";
}
@Override
public String getTag() {
return "-remoteUI";
}
@Override
public String[] getTagArgs() {
return new String[]{"port-number:client-id-string | host-string:port-number:client-id-string"};
}
@Override
public boolean isUndocumented() {
return true;
}
@Override
public boolean setString(String str) {
String[] split = str.split(":");
String hostStr = "localhost";
String portStr = null;
String clientId;
if (split.length == 3) {
hostStr = split[0];
portStr = split[1];
clientId = split[2];
} else if (split.length == 2) {
portStr = split[0];
clientId = split[1];
} else {
return false;
}
options.setRemoteUIHost(hostStr);
options.setClientId(clientId);
try {
options.setRemoteUIHostPort(Integer.parseInt(portStr));
} catch (NumberFormatException nfe) {
System.err.println("A port must be an integer");
return false;
}
return true;
}
}
/**
* Handles the -whitelist command line flag.
*/
protected static class ArgHandlerWhitelist extends ArgHandlerString {
public ArgHandlerWhitelist() {
}
@Override
public String getPurpose() {
return "Allows the user to browse URLs that match the specified regexes (comma or space separated)";
}
@Override
public String getTag() {
return "-whitelist";
}
@Override
public String[] getTagArgs() {
return new String[]{"whitelist-string"};
}
@Override
public boolean setString(String whitelistStr) {
return BrowserWidgetHostChecker.whitelistRegexes(whitelistStr);
}
}
/**
* Base options for dev mode.
*/
protected interface HostedModeBaseOptions extends PrecompileTaskOptions, OptionLogDir,
OptionNoServer, OptionPort, OptionCodeServerPort, OptionStartupURLs, OptionRemoteUI,
OptionBindAddress {
}
/**
* Concrete class to implement all hosted mode base options.
*/
@SuppressWarnings("serial")
protected static class HostedModeBaseOptionsImpl extends PrecompileTaskOptionsImpl implements
HostedModeBaseOptions {
private String bindAddress;
private int codeServerPort;
private String connectAddress;
private boolean isNoServer;
private File logDir;
private int port;
private String remoteUIClientId;
private String remoteUIHost;
private int remoteUIHostPort;
private final List<String> startupURLs = new ArrayList<String>();
@Override
public void addStartupURL(String url) {
startupURLs.add(url);
}
@Override
public boolean alsoLogToFile() {
return logDir != null;
}
@Override
public String getBindAddress() {
return bindAddress;
}
@Override
public String getClientId() {
return remoteUIClientId;
}
@Override
public int getCodeServerPort() {
return codeServerPort;
}
@Override
public String getConnectAddress() {
return connectAddress;
}
@Override
public File getLogDir() {
return logDir;
}
@Override
public File getLogFile(String sublog) {
if (logDir == null) {
return null;
}
return new File(logDir, sublog);
}
@Override
public int getPort() {
return port;
}
@Override
public String getRemoteUIHost() {
return remoteUIHost;
}
@Override
public int getRemoteUIHostPort() {
return remoteUIHostPort;
}
@Override
public List<String> getStartupURLs() {
return Collections.unmodifiableList(startupURLs);
}
@Override
public boolean isNoServer() {
return isNoServer;
}
@Override
public void setBindAddress(String bindAddress) {
this.bindAddress = bindAddress;
}
@Override
public void setClientId(String clientId) {
this.remoteUIClientId = clientId;
}
@Override
public void setCodeServerPort(int port) {
codeServerPort = port;
}
@Override
public void setConnectAddress(String connectAddress) {
this.connectAddress = connectAddress;
}
@Override
public void setLogFile(String filename) {
logDir = new File(filename);
}
@Override
public void setNoServer(boolean isNoServer) {
this.isNoServer = isNoServer;
}
@Override
public void setPort(int port) {
this.port = port;
}
@Override
public void setRemoteUIHost(String remoteUIHost) {
this.remoteUIHost = remoteUIHost;
}
@Override
public void setRemoteUIHostPort(int remoteUIHostPort) {
this.remoteUIHostPort = remoteUIHostPort;
}
@Override
public boolean useRemoteUI() {
return remoteUIHost != null;
}
}
/**
* Controls what local address to bind to.
*/
protected interface OptionBindAddress {
String getBindAddress();
String getConnectAddress();
void setBindAddress(String bindAddress);
void setConnectAddress(String connectAddress);
}
/**
* Controls what port the code server listens on.
*/
protected interface OptionCodeServerPort {
int getCodeServerPort();
void setCodeServerPort(int codeServerPort);
}
/**
* Controls whether and where to log data to file.
*
*/
protected interface OptionLogDir {
boolean alsoLogToFile();
File getLogDir();
File getLogFile(String subfile);
void setLogFile(String filename);
}
/**
* Controls whether to run a server or not.
*
*/
protected interface OptionNoServer {
boolean isNoServer();
void setNoServer(boolean isNoServer);
}
/**
* Controls what port to use.
*
*/
protected interface OptionPort {
int getPort();
void setPort(int port);
}
/**
* Controls the UI that should be used to display the dev mode server's data.
*/
protected interface OptionRemoteUI {
String getClientId();
String getRemoteUIHost();
int getRemoteUIHostPort();
void setClientId(String clientId);
void setRemoteUIHost(String remoteUIHost);
void setRemoteUIHostPort(int remoteUIHostPort);
boolean useRemoteUI();
}
/**
* Controls the startup URLs.
*/
protected interface OptionStartupURLs {
void addStartupURL(String url);
List<String> getStartupURLs();
}
/**
* The base dev mode argument processor.
*/
protected abstract static class ArgProcessor extends ArgProcessorBase {
public ArgProcessor(HostedModeBaseOptions options, boolean forceServer) {
if (!forceServer) {
registerHandler(new ArgHandlerNoServerFlag(options));
}
registerHandler(new ArgHandlerPort(options));
registerHandler(new ArgHandlerWhitelist());
registerHandler(new ArgHandlerBlacklist());
registerHandler(new ArgHandlerEnableGeneratorResultCaching());
registerHandler(new ArgHandlerLogDir(options));
registerHandler(new ArgHandlerLogLevel(options));
registerHandler(new ArgHandlerGenDir(options));
registerHandler(new ArgHandlerBindAddress(options));
registerHandler(new ArgHandlerCodeServerPort(options));
registerHandler(new ArgHandlerRemoteUI(options));
}
}
private static final boolean generatorResultCachingDisabled =
(System.getProperty("gwt.disableGeneratorResultCaching") != null);
private static final Random RNG = new Random();
public static String normalizeURL(String unknownUrlText, boolean isHttps, int port, String host) {
if (unknownUrlText.contains("://")) {
// Assume it's a full url.
return unknownUrlText;
}
// Assume it's a trailing url path.
if (unknownUrlText.length() > 0 && unknownUrlText.charAt(0) == '/') {
unknownUrlText = unknownUrlText.substring(1);
}
String protocol = "http";
String portString = ":" + port;
if (isHttps) {
protocol += "s";
if (port == 443) {
portString = "";
}
} else if (port == 80) {
portString = "";
}
return protocol + "://" + host + portString + "/" + unknownUrlText;
}
/**
* Produce a random string that has low probability of collisions.
*
* <p>
* In this case, we use 16 characters, each drawn from a pool of 94, so the
* number of possible values is 94^16, leading to an expected number of values
* used before a collision occurs as sqrt(pi/2) * 94^8 (treated the same as a
* birthday attack), or a little under 10^16.
*
* <p>
* This algorithm is also implemented in hosted.html, though it is not
* technically important that they match.
*
* @return a random string
*/
protected static String randomString() {
StringBuilder buf = new StringBuilder(16);
for (int i = 0; i < 16; ++i) {
buf.append((char) RNG.nextInt('~' - '!' + 1) + '!');
}
return buf.toString();
}
protected TreeLogger.Type baseLogLevelForUI = null;
protected String bindAddress;
protected int codeServerPort;
protected String connectAddress;
protected boolean isHttps;
protected BrowserListener listener;
protected final HostedModeBaseOptions options;
protected final CompilerContext.Builder compilerContextBuilder = new CompilerContext.Builder();
protected CompilerContext compilerContext;
protected DevModeUI ui = null;
private final Semaphore blockUntilDone = new Semaphore(0);
private BrowserWidgetHost browserHost = new UiBrowserWidgetHostImpl();
private boolean headlessMode = false;
private Map<String, RebindCache> rebindCaches = null;
private boolean started;
private TreeLogger topLogger;
public DevModeBase() {
// Set any platform specific system properties.
BootStrapPlatform.initHostedMode();
BootStrapPlatform.applyPlatformHacks();
compilerContext = compilerContextBuilder.build();
options = createOptions();
}
public final void addStartupURL(String url) {
options.addStartupURL(url);
}
/**
* Gets the base log level recommended by the UI for INFO-level messages. This
* method can only be called once {@link #createUI()} has been called. Please
* do not depend on this method, as it is subject to change.
*
* @return the log level to use for INFO-level messages
*/
public TreeLogger.Type getBaseLogLevelForUI() {
if (baseLogLevelForUI == null) {
throw new IllegalStateException("The ui must be created before calling this method.");
}
return baseLogLevelForUI;
}
public final int getPort() {
return options.getPort();
}
public TreeLogger getTopLogger() {
return topLogger;
}
/**
* Callback for the UI to indicate it is done.
*/
@Override
public void onDone() {
setDone();
}
/**
* Sets up all the major aspects of running the shell graphically, including
* creating the main window and optionally starting an embedded web server.
*/
public final void run() {
try {
// Eager AWT init for OS X to ensure safe coexistence with SWT.
BootStrapPlatform.initGui();
boolean success = startUp();
// The web server is running now, so launch browsers for startup urls.
ui.moduleLoadComplete(success);
blockUntilDone.acquire();
} catch (Exception e) {
e.printStackTrace();
} finally {
shutDown();
}
}
public final void setPort(int port) {
options.setPort(port);
}
public final void setRunTomcat(boolean run) {
options.setNoServer(!run);
}
/**
* Derived classes can override to lengthen ping delay.
*/
protected long checkForUpdatesInterval() {
return CheckForUpdates.ONE_MINUTE;
}
protected abstract HostedModeBaseOptions createOptions();
/**
* Creates an instance of ShellModuleSpaceHost (or a derived class) using the
* specified constituent parts. This method is made to be overridden for
* subclasses that need to change the behavior of ShellModuleSpaceHost.
*
* @param logger TreeLogger to use
* @param compilationState
* @param moduleDef
* @return ShellModuleSpaceHost instance
*/
protected final ShellModuleSpaceHost doCreateShellModuleSpaceHost(TreeLogger logger,
CompilationState compilationState, ModuleDef moduleDef) throws UnableToCompleteException {
ArtifactAcceptor artifactAcceptor = createArtifactAcceptor(logger, moduleDef);
return new ShellModuleSpaceHost(logger, compilationState, moduleDef, options.getGenDir(),
artifactAcceptor, getRebindCache(moduleDef.getName()));
}
protected abstract void doShutDownServer();
/**
* Perform any slower startup tasks, such as loading modules. This is separate
* from {@link #doStartup()} so that the UI can be updated as soon as possible
* and the web server can be started earlier.
*
* @return false if startup failed
*/
protected boolean doSlowStartup() {
// do nothing by default
return true;
}
protected abstract boolean doStartup();
/**
* Perform any startup tasks, including initializing the UI (if any) and the
* logger, updates checker, and the development mode code server.
*
* <p>
* Subclasses that override this method should be careful what facilities are
* used before the super implementation is called.
*
* @return true if startup was successful
*/
protected boolean doStartup(File persistentCacheDir) {
bindAddress = options.getBindAddress();
connectAddress = options.getConnectAddress();
// Create the main app window.
ui.initialize(options.getLogLevel());
topLogger = ui.getTopLogger();
compilerContext = compilerContextBuilder.unitCache(
UnitCacheSingleton.get(getTopLogger(), persistentCacheDir)).build();
// Set done callback
ui.setCallback(DoneEvent.getType(), this);
// Check for updates
if (!options.isUpdateCheckDisabled()) {
final TreeLogger logger = getTopLogger();
final CheckForUpdates updateChecker = CheckForUpdates.createUpdateChecker(logger);
if (updateChecker != null) {
Thread checkerThread = new Thread("GWT Update Checker") {
@Override
public void run() {
CheckForUpdates.logUpdateAvailable(logger, updateChecker
.check(checkForUpdatesInterval()));
}
};
checkerThread.setDaemon(true);
checkerThread.start();
}
}
// Accept connections from OOPHM clients
ensureCodeServerListener();
return true;
}
protected abstract int doStartUpServer();
protected void ensureCodeServerListener() {
if (listener == null) {
codeServerPort = options.getCodeServerPort();
listener =
new BrowserListener(getTopLogger(), bindAddress, codeServerPort, new OophmSessionHandler(
getTopLogger(), browserHost));
listener.start();
try {
// save the port we actually used if it was auto
codeServerPort = listener.getSocketPort();
} catch (UnableToCompleteException e) {
// ignore errors listening, we will catch them later
}
}
}
protected String getHost() {
return connectAddress;
}
/**
* Add any plausible HTML files which might be used as startup URLs. Found
* URLs should be added to {@code options.addStartupUrl(url)}.
*/
protected void inferStartupUrls() {
// do nothing by default
}
/**
* By default we will open the application window.
*
* @return true if we are running in headless mode
*/
protected final boolean isHeadless() {
return headlessMode;
}
/**
* Perform an initial hosted mode link, without overwriting newer or
* unmodified files in the output folder.
*
* @param logger the logger to use
* @param module the module to link
* @throws UnableToCompleteException
*/
protected final StandardLinkerContext link(TreeLogger logger, ModuleDef module)
throws UnableToCompleteException {
TreeLogger linkLogger =
logger.branch(TreeLogger.DEBUG, "Linking module '" + module.getName() + "'");
// Create a new active linker stack for the fresh link.
StandardLinkerContext linkerStack = new StandardLinkerContext(
linkLogger, module, compilerContext.getPublicResourceOracle(), options);
ArtifactSet artifacts = linkerStack.getArtifactsForPublicResources(logger, module);
artifacts = linkerStack.invokeLegacyLinkers(linkLogger, artifacts);
artifacts = linkerStack.invokeFinalLink(linkLogger, artifacts);
produceOutput(linkLogger, linkerStack, artifacts, module, false);
return linkerStack;
}
/**
* Load a module.
*
* @param logger TreeLogger to use
* @param moduleName name of the module to load
* @param refresh if <code>true</code>, refresh the module from disk
*
* @return the loaded module
* @throws UnableToCompleteException
*/
protected ModuleDef loadModule(TreeLogger logger, String moduleName, boolean refresh)
throws UnableToCompleteException {
ModuleDef moduleDef =
ModuleDefLoader.loadFromClassPath(logger, compilerContext, moduleName, refresh);
compilerContext = compilerContextBuilder.module(moduleDef).build();
assert (moduleDef != null) : "Required module state is absent";
return moduleDef;
}
protected URL processUrl(String url) throws UnableToCompleteException {
/*
* TODO(jat): properly support launching arbitrary browsers -- need some UI
* API tweaks to support that.
*/
URL parsedUrl = null;
try {
parsedUrl = new URL(url);
String path = parsedUrl.getPath();
String query = parsedUrl.getQuery();
String hash = parsedUrl.getRef();
String hostedParam =
BrowserListener.getDevModeURLParams(connectAddress, listener.getSocketPort());
if (query == null) {
query = hostedParam;
} else {
query += '&' + hostedParam;
}
path += '?' + query;
if (hash != null) {
path += '#' + hash;
}
parsedUrl = new URL(parsedUrl.getProtocol(), parsedUrl.getHost(), parsedUrl.getPort(), path);
url = parsedUrl.toExternalForm();
} catch (MalformedURLException e) {
getTopLogger().log(TreeLogger.ERROR, "Invalid URL " + url, e);
throw new UnableToCompleteException();
}
return parsedUrl;
}
protected abstract void produceOutput(TreeLogger logger, StandardLinkerContext linkerStack,
ArtifactSet artifacts, ModuleDef module, boolean isRelink) throws UnableToCompleteException;
protected final void setDone() {
blockUntilDone.release();
}
protected final void setHeadless(boolean headlessMode) {
this.headlessMode = headlessMode;
}
protected final void shutDown() {
if (options.isNoServer()) {
return;
}
doShutDownServer();
}
protected final boolean startUp() {
if (started) {
throw new IllegalStateException("Startup code has already been run");
}
Event startupEvent = SpeedTracerLogger.start(DevModeEventType.STARTUP);
try {
// See if there was a UI specified by command-line args
ui = createUI();
started = true;
if (!doStartup()) {
/*
* TODO (amitmanjhi): Adding this redundant logging to narrow down a
* failure. Remove soon.
*/
getTopLogger().log(TreeLogger.ERROR, "shell failed in doStartup method");
return false;
}
if (!options.isNoServer()) {
int resultPort = doStartUpServer();
if (resultPort < 0) {
/*
* TODO (amitmanjhi): Adding this redundant logging to narrow down a
* failure. Remove soon.
*/
getTopLogger().log(TreeLogger.ERROR, "shell failed in doStartupServer method");
return false;
}
options.setPort(resultPort);
getTopLogger().log(TreeLogger.TRACE, "Started web server on port " + resultPort);
}
if (options.getStartupURLs().isEmpty()) {
// if no URLs were supplied, try and find plausible ones
inferStartupUrls();
}
if (options.getStartupURLs().isEmpty()) {
// TODO(jat): we could walk public resources to find plausible URLs
// after the module(s) are loaded
warnAboutNoStartupUrls();
}
setStartupUrls(getTopLogger());
if (!doSlowStartup()) {
/*
* TODO (amitmanjhi): Adding this redundant logging to narrow down a
* failure. Remove soon.
*/
getTopLogger().log(TreeLogger.ERROR, "shell failed in doSlowStartup method");
return false;
}
return true;
} finally {
startupEvent.end();
}
}
/**
* Log a warning explaining that no startup URLs were specified and no
* plausible startup URLs were found.
*/
protected abstract void warnAboutNoStartupUrls();
private ArtifactAcceptor createArtifactAcceptor(TreeLogger logger, final ModuleDef module)
throws UnableToCompleteException {
final StandardLinkerContext linkerContext = link(logger, module);
return new ArtifactAcceptor() {
@Override
public void accept(TreeLogger relinkLogger, ArtifactSet newArtifacts)
throws UnableToCompleteException {
relink(relinkLogger, linkerContext, module, newArtifacts);
}
};
}
/**
* Create the UI and set the base log level for the UI.
*/
private DevModeUI createUI() {
DevModeUI newUI = null;
Event createUIEvent = SpeedTracerLogger.start(DevModeEventType.CREATE_UI);
if (headlessMode) {
newUI = new HeadlessUI(options);
} else {
if (options.useRemoteUI()) {
try {
newUI =
new RemoteUI(options.getRemoteUIHost(), options.getRemoteUIHostPort(), options
.getClientId());
baseLogLevelForUI = TreeLogger.Type.TRACE;
} catch (Throwable t) {
System.err.println("Could not connect to remote UI listening at "
+ options.getRemoteUIHost() + ":" + options.getRemoteUIHostPort()
+ ". Using default UI instead.");
}
}
}
if (newUI == null) {
newUI = new SwingUI(options);
}
if (baseLogLevelForUI == null) {
baseLogLevelForUI = TreeLogger.Type.INFO;
}
createUIEvent.end();
return newUI;
}
private RebindCache getRebindCache(String moduleName) {
if (generatorResultCachingDisabled) {
return null;
}
if (rebindCaches == null) {
rebindCaches = new HashMap<String, RebindCache>();
}
RebindCache cache = rebindCaches.get(moduleName);
if (cache == null) {
cache = new RebindCache();
rebindCaches.put(moduleName, cache);
}
return cache;
}
/**
* Perform hosted mode relink when new artifacts are generated, without
* overwriting newer or unmodified files in the output folder.
*
* @param logger the logger to use
* @param module the module to link
* @param newlyGeneratedArtifacts the set of new artifacts
* @throws UnableToCompleteException
*/
private void relink(TreeLogger logger, StandardLinkerContext linkerContext, ModuleDef module,
ArtifactSet newlyGeneratedArtifacts) throws UnableToCompleteException {
TreeLogger linkLogger =
logger.branch(TreeLogger.DEBUG, "Relinking module '" + module.getName() + "'");
ArtifactSet artifacts = linkerContext.invokeRelink(linkLogger, newlyGeneratedArtifacts);
produceOutput(linkLogger, linkerContext, artifacts, module, true);
}
/**
* Set the set of startup URLs. This is done before launching to allow the UI
* to better present the options to the user, but note that the UI should not
* attempt to launch the URLs until
* {@link DevModeUI#moduleLoadComplete(boolean)} is called, and should not
* automatically launch any URLs if they
*
* @param logger TreeLogger instance to use
*/
private void setStartupUrls(final TreeLogger logger) {
ensureCodeServerListener();
Map<String, URL> startupUrls = new HashMap<String, URL>();
for (String prenormalized : options.getStartupURLs()) {
String startupURL = normalizeURL(prenormalized, isHttps, getPort(), getHost());
logger.log(TreeLogger.DEBUG, "URL " + prenormalized + " normalized as " + startupURL, null);
try {
URL url = processUrl(startupURL);
startupUrls.put(prenormalized, url);
} catch (UnableToCompleteException e) {
logger.log(TreeLogger.ERROR, "Unable to process startup URL " + startupURL, null);
}
}
ui.setStartupUrls(startupUrls);
}
}