blob: faa78b7e6cb8f9744e753cf3521a4c9f0de4b398 [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.TreeLogger.HelpInfo;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
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.javac.CompilationState;
import com.google.gwt.dev.jjs.JJSOptions;
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.ui.DevModeUI.ModuleHandle;
import com.google.gwt.dev.util.BrowserInfo;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
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.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
/**
* The main executable class for the hosted mode shell. This class must not have
* any GUI dependencies.
*/
abstract class DevModeBase implements DoneCallback {
/**
* Implementation of BrowserWidgetHost that supports the abstract UI
* interface.
*/
public class UiBrowserWidgetHostImpl implements BrowserWidgetHost {
public ModuleSpaceHost createModuleSpaceHost(TreeLogger mainLogger,
String moduleName, String userAgent, String url, String tabKey,
String sessionKey, BrowserChannelServer serverChannel,
byte[] userAgentIcon) throws UnableToCompleteException {
if (sessionKey == null) {
// if we don't have a unique session key, make one up
sessionKey = randomString();
}
TreeLogger logger = mainLogger;
TreeLogger.Type maxLevel = options.getLogLevel();
String agentTag = BrowserInfo.getShortName(userAgent);
String remoteSocket = serverChannel.getRemoteEndpoint();
ModuleHandle module = ui.loadModule(userAgent, remoteSocket, url, tabKey,
moduleName, sessionKey, agentTag, userAgentIcon, maxLevel);
// TODO(jat): add support for closing an active module
logger = module.getLogger();
try {
// Try to find an existing loaded version of the module def.
ModuleDef moduleDef = loadModule(logger, moduleName, true);
assert (moduleDef != null);
ShellModuleSpaceHost host = doCreateShellModuleSpaceHost(logger,
moduleDef.getCompilationState(logger), moduleDef);
loadedModules.put(host, module);
return host;
} catch (RuntimeException e) {
logger.log(TreeLogger.ERROR, "Exception initializing module", e);
ui.unloadModule(module);
throw e;
}
}
public TreeLogger getLogger() {
return getTopLogger();
}
public void unloadModule(ModuleSpaceHost moduleSpaceHost) {
ModuleHandle module = loadedModules.remove(moduleSpaceHost);
if (module != null) {
ui.unloadModule(module);
}
}
}
/**
* 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 -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;
}
}
/**
* 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(getFreeSocketPort());
} 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 -portHosted command line flag.
*/
protected static class ArgHandlerPortHosted extends ArgHandlerString {
private final OptionPortHosted options;
public ArgHandlerPortHosted(OptionPortHosted options) {
this.options = options;
}
@Override
public String[] getDefaultArgs() {
return new String[] {"-portHosted", "9997"};
}
@Override
public String getPurpose() {
return "Listens on the specified port for hosted mode connections";
}
@Override
public String getTag() {
return "-portHosted";
}
@Override
public String[] getTagArgs() {
return new String[] {"port-number | \"auto\""};
}
@Override
public boolean setString(String value) {
if (value.equals("auto")) {
options.setPortHosted(getFreeSocketPort());
} else {
try {
options.setPortHosted(Integer.parseInt(value));
} catch (NumberFormatException e) {
System.err.println("A port must be an integer or \"auto\"");
return false;
}
}
return true;
}
}
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 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 {
@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 interface HostedModeBaseOptions extends JJSOptions, OptionLogDir,
OptionLogLevel, OptionGenDir, OptionNoServer, OptionPort,
OptionPortHosted, OptionStartupURLs, OptionRemoteUI {
/**
* The base shell work directory.
*
* @param moduleDef
* @return working directory base
*/
File getShellBaseWorkDir(ModuleDef moduleDef);
}
/**
* Concrete class to implement all hosted mode base options.
*/
@SuppressWarnings("serial")
protected static class HostedModeBaseOptionsImpl extends
PrecompileOptionsImpl implements HostedModeBaseOptions {
private boolean isNoServer;
private File logDir;
private int port;
private int portHosted;
private String remoteUIClientId;
private String remoteUIHost;
private int remoteUIHostPort;
private final List<String> startupURLs = new ArrayList<String>();
public void addStartupURL(String url) {
startupURLs.add(url);
}
public boolean alsoLogToFile() {
return logDir != null;
}
public String getClientId() {
return remoteUIClientId;
}
public File getLogDir() {
return logDir;
}
public File getLogFile(String sublog) {
if (logDir == null) {
return null;
}
return new File(logDir, sublog);
}
public int getPort() {
return port;
}
public int getPortHosted() {
return portHosted;
}
public String getRemoteUIHost() {
return remoteUIHost;
}
public int getRemoteUIHostPort() {
return remoteUIHostPort;
}
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 setClientId(String clientId) {
this.remoteUIClientId = clientId;
}
public void setLogFile(String filename) {
logDir = new File(filename);
}
public void setNoServer(boolean isNoServer) {
this.isNoServer = isNoServer;
}
public void setPort(int port) {
this.port = port;
}
public void setPortHosted(int port) {
portHosted = port;
}
public void setRemoteUIHost(String remoteUIHost) {
this.remoteUIHost = remoteUIHost;
}
public void setRemoteUIHostPort(int remoteUIHostPort) {
this.remoteUIHostPort = remoteUIHostPort;
}
public boolean useRemoteUI() {
return remoteUIHost != null;
}
}
/**
* 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);
}
protected interface OptionPortHosted {
int getPortHosted();
void setPortHosted(int portHosted);
}
/**
* 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();
}
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 ArgHandlerLogDir(options));
registerHandler(new ArgHandlerLogLevel(options));
registerHandler(new ArgHandlerGenDir(options));
registerHandler(new ArgHandlerPortHosted(options));
registerHandler(new ArgHandlerRemoteUI(options));
}
}
private static final Random RNG = new Random();
private static final AtomicLong uniqueId = new AtomicLong();
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;
}
}
/**
* Returns a free port. The returned port should not be returned again unless
* the ephemeral port range is exhausted.
*/
protected static int getFreeSocketPort() {
ServerSocket socket = null;
try {
socket = new ServerSocket(0);
return socket.getLocalPort();
} catch (IOException e) {
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
return -1;
}
/**
* 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 int codeServerPort;
protected BrowserListener listener;
protected final HostedModeBaseOptions options;
protected DevModeUI ui = null;
/**
* 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 final Semaphore blockUntilDone = new Semaphore(0);
private BrowserWidgetHost browserHost = new UiBrowserWidgetHostImpl();
private boolean headlessMode = false;
private final Map<ModuleSpaceHost, ModuleHandle> loadedModules = new IdentityHashMap<ModuleSpaceHost, ModuleHandle>();
private boolean started;
private TreeLogger topLogger;
public DevModeBase() {
// Set any platform specific system properties.
BootStrapPlatform.initHostedMode();
BootStrapPlatform.applyPlatformHacks();
options = createOptions();
}
public final void addStartupURL(String url) {
options.addStartupURL(url);
}
public final int getPort() {
return options.getPort();
}
public TreeLogger getTopLogger() {
return topLogger;
}
/**
* Launch the arguments as Urls in separate windows.
*
* @param logger TreeLogger instance to use
*/
public void launchStartupUrls(final TreeLogger logger) {
ensureCodeServerListener();
String startupURL = "";
try {
for (String prenormalized : options.getStartupURLs()) {
startupURL = normalizeURL(prenormalized, getPort(), getHost());
logger.log(TreeLogger.INFO, "Starting URL: " + startupURL, null);
launchURL(startupURL);
}
} catch (UnableToCompleteException e) {
logger.log(TreeLogger.ERROR,
"Unable to open new window for startup URL: " + startupURL, null);
}
}
/**
* Callback for the UI to indicate it is done.
*/
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();
if (startUp()) {
// The web server is running now, so launch browsers for startup urls.
launchStartupUrls(getTopLogger());
}
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 typeOracle
* @param moduleDef
* @return ShellModuleSpaceHost instance
*/
protected final ShellModuleSpaceHost doCreateShellModuleSpaceHost(
TreeLogger logger, CompilationState compilationState, ModuleDef moduleDef)
throws UnableToCompleteException {
// Clear out the shell temp directory.
File shellBaseWorkDir = options.getShellBaseWorkDir(moduleDef);
File sessionWorkDir = new File(shellBaseWorkDir,
String.valueOf(uniqueId.getAndIncrement()));
Util.recursiveDelete(sessionWorkDir, false);
ArtifactAcceptor artifactAcceptor = createArtifactAcceptor(logger,
moduleDef);
return new ShellModuleSpaceHost(logger, compilationState, moduleDef,
options.getGenDir(), new File(sessionWorkDir, "gen"), artifactAcceptor);
}
protected abstract void doShutDownServer();
/**
* 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() {
// Create the main app window.
ui.initialize(options.getLogLevel());
topLogger = ui.getTopLogger();
// Set done callback
ui.setCallback(DoneEvent.getType(), this);
// Check for updates
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.getPortHosted();
listener = new BrowserListener(getTopLogger(), codeServerPort,
new OophmSessionHandler(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 "localhost";
}
/**
* By default we will open the application window.
*
* @return true if we are running in headless mode
*/
protected final boolean isHeadless() {
return headlessMode;
}
protected void launchURL(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(listener.getEndpointIdentifier());
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();
}
System.err.println("Using a browser with the GWT Developer Plugin, please browse to");
System.err.println("the following URL:");
System.err.println(" " + url);
final URL helpInfoUrl = parsedUrl;
getTopLogger().log(TreeLogger.INFO,
"Waiting for browser connection to " + url, null, new HelpInfo() {
@Override
public String getAnchorText() {
return "Launch default browser";
}
@Override
public String getPrefix() {
return "";
}
@Override
public URL getURL() {
return helpInfoUrl;
}
});
}
/**
* 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, options);
ArtifactSet artifacts = linkerStack.invokeLink(linkLogger);
produceOutput(linkLogger, linkerStack, artifacts, module);
return linkerStack;
}
/**
* 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 produceOutput(TreeLogger logger,
StandardLinkerContext linkerStack, ArtifactSet artifacts, ModuleDef module)
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");
}
// See if there was a UI specified by command-line args
ui = createUI();
started = true;
if (!doStartup()) {
return false;
}
if (!options.isNoServer()) {
int resultPort = doStartUpServer();
if (resultPort < 0) {
return false;
}
options.setPort(resultPort);
getTopLogger().log(TreeLogger.INFO,
"Started web server on port " + resultPort);
}
return true;
}
private ArtifactAcceptor createArtifactAcceptor(TreeLogger logger,
final ModuleDef module) throws UnableToCompleteException {
final StandardLinkerContext linkerContext = link(logger, module);
return new ArtifactAcceptor() {
public void accept(TreeLogger relinkLogger, ArtifactSet newArtifacts)
throws UnableToCompleteException {
relink(relinkLogger, linkerContext, module, newArtifacts);
}
};
}
private DevModeUI createUI() {
if (headlessMode) {
return new HeadlessUI(options);
} else {
if (options.useRemoteUI()) {
return new RemoteUI(options.getRemoteUIHost(),
options.getRemoteUIHostPort(), options.getClientId(),
options.getPort(), options.getPortHosted());
}
}
return new SwingUI(options);
}
/**
* 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);
}
}