blob: 060878c55deb02abe7c76c144fe63fa6b9e8ef08 [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.dev;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.jjs.JJSOptions;
import com.google.gwt.dev.shell.ArtifactAcceptor;
import com.google.gwt.dev.shell.BrowserWidget;
import com.google.gwt.dev.shell.BrowserWidgetHost;
import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
import com.google.gwt.dev.shell.BrowserWindowController;
import com.google.gwt.dev.shell.CheckForUpdates;
import com.google.gwt.dev.shell.ModuleSpaceHost;
import com.google.gwt.dev.shell.PlatformSpecific;
import com.google.gwt.dev.shell.ShellModuleSpaceHost;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
import com.google.gwt.dev.util.arg.OptionGenDir;
import com.google.gwt.dev.util.arg.OptionLogLevel;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The main executable class for the hosted mode shell. This class must not have
* any GUI dependencies.
*
* TODO: remove BrowserWidget references (which reference SWT via inheritance,
* though it doesn't appear to cause any harm currently.
*/
abstract class HostedModeBase implements BrowserWindowController {
/**
* Handles the -blacklist command line argument.
*/
protected static class ArgHandlerBlacklist extends ArgHandlerString {
@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 -noserver command line flag.
*/
protected static class ArgHandlerNoServerFlag extends ArgHandlerFlag {
private final OptionNoServer options;
public ArgHandlerNoServerFlag(OptionNoServer options) {
this.options = options;
}
@Override
public String getPurpose() {
return "Prevents the embedded web server from running";
}
@Override
public String getTag() {
return "-noserver";
}
@Override
public boolean setFlag() {
options.setNoServer(true);
return true;
}
}
/**
* 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 -whitelist command line flag.
*/
protected static class ArgHandlerWhitelist extends ArgHandlerString {
@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);
}
}
protected abstract class BrowserWidgetHostImpl implements BrowserWidgetHost {
public void compile() throws UnableToCompleteException {
if (isLegacyMode()) {
throw new UnsupportedOperationException();
}
HostedModeBase.this.compile(getLogger());
}
public void compile(String[] moduleNames) throws UnableToCompleteException {
if (!isLegacyMode()) {
throw new UnsupportedOperationException();
}
for (int i = 0; i < moduleNames.length; i++) {
String moduleName = moduleNames[i];
ModuleDef moduleDef = loadModule(getLogger(), moduleName, true);
HostedModeBase.this.compile(getLogger(), moduleDef);
}
}
public abstract ModuleSpaceHost createModuleSpaceHost(TreeLogger logger,
BrowserWidget widget, String moduleName)
throws UnableToCompleteException;
public TreeLogger getLogger() {
return getTopLogger();
}
public boolean initModule(String moduleName) {
return HostedModeBase.this.initModule(moduleName);
}
@Deprecated
public boolean isLegacyMode() {
return HostedModeBase.this instanceof GWTShell;
}
public String normalizeURL(String whatTheUserTyped) {
return HostedModeBase.this.normalizeURL(whatTheUserTyped);
}
public BrowserWidget openNewBrowserWindow()
throws UnableToCompleteException {
return HostedModeBase.this.openNewBrowserWindow();
}
}
protected interface HostedModeBaseOptions extends JJSOptions, OptionLogLevel,
OptionGenDir, OptionNoServer, OptionPort, OptionStartupURLs {
/**
* The base shell work directory.
*/
File getShellBaseWorkDir(ModuleDef moduleDef);
}
/**
* Concrete class to implement all hosted mode base options.
*/
protected static class HostedModeBaseOptionsImpl extends
PrecompileOptionsImpl implements HostedModeBaseOptions {
private boolean isNoServer;
private int port;
private final List<String> startupURLs = new ArrayList<String>();
public void addStartupURL(String url) {
startupURLs.add(url);
}
public int getPort() {
return port;
}
public File getShellBaseWorkDir(ModuleDef moduleDef) {
return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
}
public List<String> getStartupURLs() {
return Collections.unmodifiableList(startupURLs);
}
public boolean isNoServer() {
return isNoServer;
}
public void setNoServer(boolean isNoServer) {
this.isNoServer = isNoServer;
}
public void setPort(int port) {
this.port = port;
}
}
/**
* 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 startup URLs.
*/
protected interface OptionStartupURLs {
void addStartupURL(String url);
List<String> getStartupURLs();
}
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 ArgHandlerLogLevel(options));
registerHandler(new ArgHandlerGenDir(options));
registerHandler(new ArgHandlerScriptStyle(options));
registerHandler(new ArgHandlerEnableAssertions(options));
registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
}
}
public static String normalizeURL(String unknownUrlText, int port, String host) {
if (unknownUrlText.indexOf(":") != -1) {
// 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);
}
if (port != 80) {
// CHECKSTYLE_OFF: Not really an assembled error message, so no space
// after ':'.
return "http://" + host + ":" + port + "/" + unknownUrlText;
// CHECKSTYLE_ON
} else {
return "http://" + host + "/" + unknownUrlText;
}
}
protected final HostedModeBaseOptions options;
/**
* Cheat on the first load's refresh by assuming the module loaded by
* {@link com.google.gwt.dev.shell.GWTShellServlet} is still fresh. This
* prevents a double-refresh on startup. Subsequent refreshes will trigger a
* real refresh.
*/
private Set<String> alreadySeenModules = new HashSet<String>();
private boolean headlessMode = false;
private boolean started;
public HostedModeBase() {
// Set any platform specific system properties.
BootStrapPlatform.initHostedMode();
BootStrapPlatform.applyPlatformHacks();
options = createOptions();
}
public final void addStartupURL(String url) {
options.addStartupURL(url);
}
public abstract void closeAllBrowserWindows();
public final int getPort() {
return options.getPort();
}
public abstract TreeLogger getTopLogger();
public abstract boolean hasBrowserWindowsOpen();
/**
* Launch the arguments as Urls in separate windows.
*/
public abstract void launchStartupUrls(final TreeLogger logger);
public final String normalizeURL(String unknownUrlText) {
return normalizeURL(unknownUrlText, getPort(), getHost());
}
/**
* Sets up all the major aspects of running the shell graphically, including
* creating the main window and optionally starting the embedded Tomcat
* server.
*/
public final void run() {
try {
if (startUp()) {
// Eager AWT init for OS X to ensure safe coexistence with SWT.
BootStrapPlatform.initGui();
// Tomcat's running now, so launch browsers for startup urls now.
launchStartupUrls(getTopLogger());
}
pumpEventLoop();
} 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;
}
/**
* Compiles all modules.
*/
protected abstract void compile(TreeLogger logger)
throws UnableToCompleteException;
/**
* Compiles a module (legacy only).
*/
@Deprecated
protected abstract void compile(TreeLogger logger, ModuleDef moduleDef)
throws UnableToCompleteException;
protected abstract HostedModeBaseOptions createOptions();
protected abstract ArtifactAcceptor doCreateArtifactAcceptor(ModuleDef module);
/**
* 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 typeOracle
* @param moduleDef
* @param genDir
* @return ShellModuleSpaceHost instance
*/
protected final ShellModuleSpaceHost doCreateShellModuleSpaceHost(
TreeLogger logger, TypeOracle typeOracle, ModuleDef moduleDef) {
// Clear out the shell temp directory.
Util.recursiveDelete(options.getShellBaseWorkDir(moduleDef), true);
return new ShellModuleSpaceHost(logger, typeOracle, moduleDef,
options.getGenDir(), new File(options.getShellBaseWorkDir(moduleDef),
"gen"), doCreateArtifactAcceptor(moduleDef));
}
protected abstract void doShutDownServer();
protected boolean doStartup() {
loadRequiredNativeLibs();
// Create the main app window.
openAppWindow();
// Initialize the logger.
//
initializeLogger();
// Check for updates
final TreeLogger logger = getTopLogger();
final CheckForUpdates updateChecker = PlatformSpecific.createUpdateChecker(logger);
if (updateChecker != null) {
Thread checkerThread = new Thread("GWT Update Checker") {
@Override
public void run() {
PlatformSpecific.logUpdateAvailable(logger,
updateChecker.check(checkForUpdatesInterval()));
}
};
checkerThread.setDaemon(true);
checkerThread.start();
}
return true;
}
protected abstract int doStartUpServer();
protected String getHost() {
return "localhost";
}
protected abstract void initializeLogger();
/**
* Called from a selection script as it begins to load in hosted mode. This
* triggers a hosted mode link, which might actually update the running
* selection script.
*
* @param moduleName the module to link
* @return <code>true</code> if the selection script was overwritten; this
* will trigger a full-page refresh by the calling (out of date)
* selection script
*/
protected abstract boolean initModule(String moduleName);
/**
* By default we will open the application window.
*
* @return true if we are running in headless mode
*/
protected final boolean isHeadless() {
return headlessMode;
}
/**
* Load a module.
*
* @param moduleName name of the module to load
* @param logger TreeLogger to use
* @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 {
refresh &= alreadySeenModules.contains(moduleName);
ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger, moduleName,
refresh);
alreadySeenModules.add(moduleName);
assert (moduleDef != null) : "Required module state is absent";
return moduleDef;
}
protected abstract void loadRequiredNativeLibs();
protected abstract boolean notDone();
protected abstract void openAppWindow();
protected abstract void processEvents() throws Exception;
protected final void pumpEventLoop() {
TreeLogger logger = getTopLogger();
// Run the event loop. When there are no open shells, quit.
//
while (notDone()) {
try {
processEvents();
} catch (Throwable e) {
String msg = e.getMessage();
msg = (msg != null ? msg : e.getClass().getName());
logger.log(TreeLogger.ERROR, msg, e);
}
}
}
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");
}
started = true;
if (!doStartup()) {
return false;
}
if (!options.isNoServer()) {
int resultPort = doStartUpServer();
if (resultPort < 0) {
return false;
}
options.setPort(resultPort);
}
return true;
}
}