Merging releases/1.6@4130:4147 into trunk.
svn merge --accept postpone -r4130:4147 https://google-web-toolkit.googlecode.com/svn/releases/1.6 .
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4148 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
index 2b4f0f8..716995e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -17,6 +17,7 @@
import com.google.gwt.core.ext.linker.CompilationResult;
import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.dev.PermutationResult;
import com.google.gwt.dev.util.Util;
import java.io.File;
@@ -68,19 +69,18 @@
*/
public static final Comparator<SortedMap<SelectionProperty, String>> MAP_COMPARATOR = new MapComparator();
- private final File cacheFile;
+ private final File resultFile;
private transient SoftReference<String[]> js;
private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
MAP_COMPARATOR);
private final String strongName;
- public StandardCompilationResult(String[] js, String strongName,
- File cacheFile) {
+ public StandardCompilationResult(String[] js, String strongName, File resultFile) {
super(StandardLinkerContext.class);
this.js = new SoftReference<String[]>(js);
this.strongName = strongName;
- this.cacheFile = cacheFile;
+ this.resultFile = resultFile;
}
/**
@@ -102,12 +102,20 @@
}
if (toReturn == null) {
- byte[][] bytes = Util.readFileAndSplit(cacheFile);
- if (bytes == null) {
- throw new RuntimeException("Unexpectedly unable to read JS file '"
- + cacheFile.getAbsolutePath() + "'");
+ PermutationResult result;
+ try {
+ result = Util.readFileAsObject(resultFile, PermutationResult.class);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(
+ "Unexpectedly unable to read PermutationResult '"
+ + resultFile.getAbsolutePath() + "'", e);
}
- toReturn = Util.toStrings(bytes);
+ if (result == null) {
+ throw new RuntimeException(
+ "Unexpectedly unable to read PermutationResult '"
+ + resultFile.getAbsolutePath() + "'");
+ }
+ toReturn = result.getJs();
js = new SoftReference<String[]>(toReturn);
}
return toReturn;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 26353b1..cb9619a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -26,6 +26,7 @@
import com.google.gwt.core.ext.linker.PublicResource;
import com.google.gwt.core.ext.linker.SelectionProperty;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.dev.PermutationResult;
import com.google.gwt.dev.cfg.BindingProperty;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.Property;
@@ -261,21 +262,27 @@
/**
* Gets or creates a CompilationResult for the given JavaScript program.
*/
- public StandardCompilationResult getCompilation(TreeLogger logger, File jsFile)
- throws UnableToCompleteException {
- byte[][] results = Util.readFileAndSplit(jsFile);
-
- if (results == null) {
- logger.log(TreeLogger.ERROR, "Unable to read file '"
- + jsFile.getAbsolutePath() + "'");
+ public StandardCompilationResult getCompilation(TreeLogger logger,
+ File resultFile) throws UnableToCompleteException {
+ PermutationResult permutationResult;
+ try {
+ permutationResult = Util.readFileAsObject(resultFile,
+ PermutationResult.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(TreeLogger.ERROR, "Unable to instantiate PermutationResult", e);
throw new UnableToCompleteException();
}
- String strongName = Util.computeStrongName(results);
+ if (permutationResult == null) {
+ logger.log(TreeLogger.ERROR, "Unable to read PermutationResult");
+ throw new UnableToCompleteException();
+ }
+
+ String strongName = Util.computeStrongName(Util.getBytes(permutationResult.getJs()));
StandardCompilationResult result = resultsByStrongName.get(strongName);
if (result == null) {
- result = new StandardCompilationResult(Util.toStrings(results), strongName,
- jsFile);
+ result = new StandardCompilationResult(permutationResult.getJs(),
+ strongName, resultFile);
resultsByStrongName.put(result.getStrongName(), result);
artifacts.add(result);
}
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index 55c5999..ec0e924 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -18,10 +18,10 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.CompileTaskRunner.CompileTask;
-import com.google.gwt.dev.PermutationCompiler.ResultsHandler;
-import com.google.gwt.dev.jjs.UnifiedAst;
import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
+import com.google.gwt.dev.jjs.UnifiedAst;
import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
import com.google.gwt.util.tools.ArgHandlerString;
import java.io.File;
@@ -29,15 +29,16 @@
import java.util.TreeSet;
/**
- * Performs the second phase of compilation, converting the Precompile's AST into
- * JavaScript outputs.
+ * Performs the second phase of compilation, converting the Precompile's AST
+ * into JavaScript outputs.
*/
public class CompilePerms {
/**
* Options for CompilePerms.
*/
- public interface CompilePermsOptions extends CompileTaskOptions, OptionPerms {
+ public interface CompilePermsOptions extends CompileTaskOptions,
+ OptionLocalWorkers, OptionPerms {
}
/**
@@ -121,6 +122,7 @@
public ArgProcessor(CompilePermsOptions options) {
super(options);
registerHandler(new ArgHandlerPerms(options));
+ registerHandler(new ArgHandlerLocalWorkers(options));
}
@Override
@@ -135,6 +137,7 @@
static class CompilePermsOptionsImpl extends CompileTaskOptionsImpl implements
CompilePermsOptions {
+ private int localWorkers;
private int[] permsToCompile = new int[0];
public CompilePermsOptionsImpl() {
@@ -146,24 +149,40 @@
public void copyFrom(CompilePermsOptions other) {
super.copyFrom(other);
-
setPermsToCompile(other.getPermsToCompile());
+ setLocalWorkers(other.getLocalWorkers());
+ }
+
+ public int getLocalWorkers() {
+ return localWorkers;
}
public int[] getPermsToCompile() {
return permsToCompile.clone();
}
+ public void setLocalWorkers(int localWorkers) {
+ this.localWorkers = localWorkers;
+ }
+
public void setPermsToCompile(int[] permsToCompile) {
this.permsToCompile = permsToCompile.clone();
}
}
- public static String[] compile(TreeLogger logger, Permutation permutation,
- UnifiedAst unifiedAst) {
+ /**
+ * Compile a single permutation.
+ */
+ public static PermutationResult compile(TreeLogger logger,
+ Permutation permutation, UnifiedAst unifiedAst) {
try {
- return JavaToJavaScriptCompiler.compilePermutation(logger, unifiedAst,
- permutation.getRebindAnswers());
+ final String js[] = JavaToJavaScriptCompiler.compilePermutation(logger,
+ unifiedAst, permutation.getRebindAnswers());
+ return new PermutationResult() {
+ public String[] getJs() {
+ return js;
+ }
+ };
} catch (UnableToCompleteException e) {
// We intentionally don't pass in the exception here since the real
// cause has been logged.
@@ -171,6 +190,20 @@
}
}
+ /**
+ * Compile multiple permutations.
+ */
+ public static boolean compile(TreeLogger logger,
+ Precompilation precompilation, Permutation[] perms, int localWorkers,
+ File[] resultFiles) throws UnableToCompleteException {
+ final TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling "
+ + perms.length + " permutations");
+ PermutationWorkerFactory.compilePermutations(logger, precompilation, perms,
+ localWorkers, resultFiles);
+ branch.log(TreeLogger.INFO, "Permutation compile succeeded");
+ return true;
+ }
+
public static void main(String[] args) {
/*
* NOTE: main always exits with a call to System.exit to terminate any
@@ -194,6 +227,14 @@
System.exit(1);
}
+ public static File[] makeResultFiles(File compilerWorkDir, Permutation[] perms) {
+ File[] resultFiles = new File[perms.length];
+ for (int i = 0; i < perms.length; ++i) {
+ resultFiles[i] = makePermFilename(compilerWorkDir, perms[i].getId());
+ }
+ return resultFiles;
+ }
+
/**
* Return the filename corresponding to the given permutation number,
* one-based.
@@ -229,17 +270,15 @@
+ precompilationFile.getAbsolutePath() + "'", e);
return false;
}
- Permutation[] perms = precompilation.getPermutations();
- UnifiedAst unifiedAst = precompilation.getUnifiedAst();
- int[] permsToRun = options.getPermsToCompile();
+ Permutation[] perms = precompilation.getPermutations();
+ Permutation[] subPerms;
+ int[] permsToRun = options.getPermsToCompile();
if (permsToRun.length == 0) {
- // Compile them all.
- permsToRun = new int[perms.length];
- for (int i = 0; i < permsToRun.length; ++i) {
- permsToRun[i] = i;
- }
+ subPerms = perms;
} else {
+ int i = 0;
+ subPerms = new Permutation[permsToRun.length];
// Range check the supplied perms.
for (int permToRun : permsToRun) {
if (permToRun >= perms.length) {
@@ -248,21 +287,12 @@
+ (perms.length - 1) + "'");
return false;
}
+ subPerms[i++] = perms[permToRun];
}
}
- final TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling "
- + permsToRun.length + " permutations");
- PermutationCompiler multiThread = new PermutationCompiler(branch,
- unifiedAst, perms, permsToRun);
- multiThread.go(new ResultsHandler() {
- public void addResult(Permutation permutation, int permNum, String[] js)
- throws UnableToCompleteException {
- Util.writeStringsAsFile(branch, makePermFilename(
- options.getCompilerWorkDir(), permNum), js);
- }
- });
- branch.log(TreeLogger.INFO, "Permutation compile succeeded");
- return true;
+ File[] resultFiles = makeResultFiles(options.getCompilerWorkDir(), perms);
+ return compile(logger, precompilation, subPerms, options.getLocalWorkers(),
+ resultFiles);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
new file mode 100644
index 0000000..ace3385
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
@@ -0,0 +1,309 @@
+/*
+ * 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.TreeLogger.Type;
+import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
+import com.google.gwt.dev.util.arg.OptionLogLevel;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.util.tools.ArgHandlerString;
+import com.google.gwt.util.tools.ToolBase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+/**
+ * An out-of-process implementation of CompilePerms that will connect back to an
+ * existing compiler host. This class is intended to be launched by
+ * {@link ExternalPermutationWorkerFactory} and not by users directly.
+ */
+public class CompilePermsServer {
+ /**
+ * Adds host and port information.
+ */
+ public interface CompileServerOptions extends OptionLogLevel {
+ String getCompileHost();
+
+ int getCompilePort();
+
+ String getCookie();
+
+ void setCompileHost(String host);
+
+ void setCompilePort(int port);
+
+ void setCookie(String cookie);
+ }
+
+ static final class ArgHandlerCompileHost extends ArgHandlerString {
+ private final CompileServerOptions options;
+
+ public ArgHandlerCompileHost(CompileServerOptions options) {
+ this.options = options;
+ }
+
+ @Override
+ public String getPurpose() {
+ return "The host to which the permutation server should connect";
+ }
+
+ @Override
+ public String getTag() {
+ return "-host";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"hostname"};
+ }
+
+ @Override
+ public boolean isRequired() {
+ return true;
+ }
+
+ @Override
+ public boolean setString(String str) {
+ options.setCompileHost(str);
+ return true;
+ }
+ }
+
+ static final class ArgHandlerCompilePort extends ArgHandlerString {
+ private final CompileServerOptions options;
+
+ public ArgHandlerCompilePort(CompileServerOptions options) {
+ this.options = options;
+ }
+
+ @Override
+ public String getPurpose() {
+ return "The port to which the permutation server should connect";
+ }
+
+ @Override
+ public String getTag() {
+ return "-port";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"1234"};
+ }
+
+ @Override
+ public boolean isRequired() {
+ return true;
+ }
+
+ @Override
+ public boolean setString(String str) {
+ Integer port = Integer.parseInt(str);
+ if (port <= 0) {
+ return false;
+ }
+ options.setCompilePort(port);
+ return true;
+ }
+ }
+
+ static final class ArgHandlerCookie extends ArgHandlerString {
+
+ private final CompileServerOptions options;
+
+ public ArgHandlerCookie(CompileServerOptions option) {
+ this.options = option;
+ }
+
+ @Override
+ public String getPurpose() {
+ return "Specifies the security cookie the server expects";
+ }
+
+ @Override
+ public String getTag() {
+ return "-cookie";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"cookie"};
+ }
+
+ @Override
+ public boolean isRequired() {
+ return true;
+ }
+
+ @Override
+ public boolean setString(String str) {
+ options.setCookie(str);
+ return true;
+ }
+ }
+
+ static class ArgProcessor extends ToolBase {
+ public ArgProcessor(CompileServerOptions options) {
+ registerHandler(new ArgHandlerLogLevel(options));
+ registerHandler(new ArgHandlerCompileHost(options));
+ registerHandler(new ArgHandlerCompilePort(options));
+ registerHandler(new ArgHandlerCookie(options));
+ }
+
+ /*
+ * Overridden to make public.
+ */
+ @Override
+ public final boolean processArgs(String[] args) {
+ return super.processArgs(args);
+ }
+
+ @Override
+ protected String getName() {
+ return CompilePermsServer.class.getName();
+ }
+ }
+
+ static class CompileServerOptionsImpl implements CompileServerOptions {
+
+ private String compileHost;
+ private int compilePort;
+ private String cookie;
+ private Type logLevel;
+
+ public void copyFrom(CompileServerOptions other) {
+ setCompileHost(other.getCompileHost());
+ setCompilePort(other.getCompilePort());
+ setCookie(other.getCookie());
+ setLogLevel(other.getLogLevel());
+ }
+
+ public String getCompileHost() {
+ return compileHost;
+ }
+
+ public int getCompilePort() {
+ return compilePort;
+ }
+
+ public String getCookie() {
+ return cookie;
+ }
+
+ public Type getLogLevel() {
+ return logLevel;
+ }
+
+ public void setCompileHost(String host) {
+ assert host != null;
+ compileHost = host;
+ }
+
+ public void setCompilePort(int port) {
+ assert port > 0;
+ compilePort = port;
+ }
+
+ public void setCookie(String cookie) {
+ this.cookie = cookie;
+ }
+
+ public void setLogLevel(Type logLevel) {
+ this.logLevel = logLevel;
+ }
+ }
+
+ public static void main(String[] args) {
+ final CompileServerOptions options = new CompileServerOptionsImpl();
+ if (new ArgProcessor(options).processArgs(args)) {
+ PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
+ logger.setMaxDetail(options.getLogLevel());
+ if (run(options, logger)) {
+ System.exit(0);
+ }
+ }
+ System.exit(-1);
+ }
+
+ public static boolean run(CompileServerOptions options, TreeLogger logger) {
+ try {
+ Socket s = new Socket(options.getCompileHost(), options.getCompilePort());
+ logger.log(TreeLogger.DEBUG, "Socket opened");
+
+ ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());
+ ObjectInputStream in = new ObjectInputStream(s.getInputStream());
+
+ // Write my cookie
+ out.writeUTF(options.getCookie());
+ out.flush();
+
+ // Read the File that contains the serialized UnifiedAst
+ File astFile = (File) in.readObject();
+ ObjectInputStream astIn = new ObjectInputStream(new FileInputStream(
+ astFile));
+ UnifiedAst ast = (UnifiedAst) astIn.readObject();
+ ast.prepare();
+ logger.log(TreeLogger.SPAM, "Created new UnifiedAst instance");
+
+ // Report on the amount of memory we think we're using
+ long estimatedMemory = Runtime.getRuntime().totalMemory()
+ - Runtime.getRuntime().freeMemory();
+ out.writeLong(estimatedMemory);
+ out.flush();
+
+ boolean keepGoing = in.readBoolean();
+ while (keepGoing) {
+ compilePermutation(logger, ast, in, out);
+
+ keepGoing = in.readBoolean();
+ logger.log(TreeLogger.SPAM, "keepGoing = " + keepGoing);
+ }
+
+ logger.log(TreeLogger.DEBUG, "Successfully terminating");
+ return true;
+
+ } catch (UnknownHostException e) {
+ logger.log(TreeLogger.ERROR, "Invalid hostname", e);
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Communication error", e);
+ } catch (ClassNotFoundException e) {
+ logger.log(TreeLogger.ERROR, "Probable client/server mismatch or "
+ + "classpath misconfiguration", e);
+ }
+ return false;
+ }
+
+ static void compilePermutation(TreeLogger logger, UnifiedAst ast,
+ ObjectInputStream in, ObjectOutputStream out)
+ throws ClassNotFoundException, IOException {
+ Permutation p = (Permutation) in.readObject();
+ logger.log(TreeLogger.SPAM, "Permutation read");
+
+ PermutationResult result = CompilePerms.compile(logger.branch(
+ TreeLogger.DEBUG, "Compiling"), p, ast);
+ logger.log(TreeLogger.DEBUG, "Successfully compiled permutation");
+ out.writeObject(result);
+ out.flush();
+ logger.log(TreeLogger.SPAM, "Sent result");
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
index e0b7ead..e92d1c8 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
@@ -24,8 +24,6 @@
*/
class CompileTaskOptionsImpl implements CompileTaskOptions {
- public static final String GWT_TMP_DIR = "gwt-tmp";
-
private Type logLevel;
private String moduleName;
private boolean useGuiLogger;
diff --git a/dev/core/src/com/google/gwt/dev/CompilerOptions.java b/dev/core/src/com/google/gwt/dev/CompilerOptions.java
index 0b568e4..382eee8 100644
--- a/dev/core/src/com/google/gwt/dev/CompilerOptions.java
+++ b/dev/core/src/com/google/gwt/dev/CompilerOptions.java
@@ -21,5 +21,6 @@
/**
* The complete set of options for the GWT compiler.
*/
-public interface CompilerOptions extends PrecompileOptions, LinkOptions {
+public interface CompilerOptions extends PrecompileOptions, LinkOptions,
+ OptionLocalWorkers {
}
diff --git a/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java
new file mode 100644
index 0000000..392afea
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/ExternalPermutationWorkerFactory.java
@@ -0,0 +1,376 @@
+/*
+ * 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.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.Util;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.management.ManagementFactory;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * A PermutationWorkerFactory designed to launch instances of
+ * {@link CompilePermsServer}. The system property
+ * {@value #JAVA_COMMAND_PROPERTY} can be used to change the command used to
+ * launch the JVM. The system property {@link #JVM_ARGS_PROPERTY} can be used to
+ * override the JVM args passed to the subprocess.
+ */
+public class ExternalPermutationWorkerFactory extends PermutationWorkerFactory {
+
+ /**
+ * Allows accept() to be called a finite number of times on a ServerSocket
+ * before closing the socket.
+ */
+ private static class CountedServerSocket {
+ private int accepts;
+ private ServerSocket sock;
+
+ public CountedServerSocket(ServerSocket sock, int maxAccepts) {
+ assert sock != null;
+ assert maxAccepts >= 1;
+
+ this.accepts = maxAccepts;
+ this.sock = sock;
+ }
+
+ public synchronized Socket accept() throws IOException {
+ assert accepts >= 0;
+
+ if (accepts == 0) {
+ throw new IllegalStateException("Too many calls to accept()");
+ }
+
+ try {
+ return sock.accept();
+ } finally {
+ if (--accepts == 0) {
+ sock.close();
+ sock = null;
+ }
+ }
+ }
+ }
+
+ private static class ExternalPermutationWorker implements PermutationWorker {
+ private final File astFile;
+ private final Set<String> cookies;
+ private ObjectInputStream in;
+ private ObjectOutputStream out;
+ private final CountedServerSocket serverSocket;
+ private Socket workerSocket;
+
+ public ExternalPermutationWorker(CountedServerSocket sock, File astFile,
+ Set<String> cookies) {
+ this.astFile = astFile;
+ this.cookies = cookies;
+ this.serverSocket = sock;
+ }
+
+ public PermutationResult compile(TreeLogger logger, Permutation permutation)
+ throws TransientWorkerException, UnableToCompleteException {
+
+ // If we've just started, we need to get a connection from a subprocess
+ if (workerSocket == null) {
+ try {
+ /*
+ * We've set SO_TIMEOUT, so this may fail if the remote process never
+ * connects back.
+ */
+ workerSocket = serverSocket.accept();
+
+ in = new ObjectInputStream(workerSocket.getInputStream());
+ out = new ObjectOutputStream(workerSocket.getOutputStream());
+
+ // Verify we're talking to the right worker
+ String c = in.readUTF();
+ if (!cookies.contains(c)) {
+ throw new TransientWorkerException("Received unknown cookie " + c,
+ null);
+ }
+
+ out.writeObject(astFile);
+
+ // Get the remote worker's estimate of memory use
+ long memoryUse = in.readLong();
+ logger.log(TreeLogger.SPAM, "Remote process indicates " + memoryUse
+ + " bytes of memory used");
+
+ } catch (SocketTimeoutException e) {
+ throw new TransientWorkerException(
+ "Remote process did not connect within timeout period", e);
+ } catch (IOException e) {
+ throw new TransientWorkerException(
+ "Unable to communicate with worker", e);
+ }
+ }
+
+ try {
+ out.writeBoolean(true);
+ out.writeObject(permutation);
+ out.flush();
+ return (PermutationResult) in.readObject();
+ } catch (IOException e) {
+ logger.log(TreeLogger.WARN, "Lost communication with remote process", e);
+ throw new TransientWorkerException(
+ "Lost communication with remote process", e);
+ } catch (ClassNotFoundException e) {
+ logger.log(TreeLogger.ERROR, "Unable to receive response", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ public String getName() {
+ return "External worker "
+ + (workerSocket != null ? workerSocket.getRemoteSocketAddress()
+ : "unconnected");
+ }
+
+ public void shutdown() {
+ if (out != null) {
+ try {
+ out.writeBoolean(false);
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ // Not much to do here
+ }
+ }
+
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // Not much to do here
+ }
+ }
+
+ if (workerSocket != null) {
+ try {
+ workerSocket.close();
+ } catch (IOException e) {
+ // Nothing to do
+ }
+ }
+ }
+ }
+
+ /**
+ * A system property that can be used to override the command used to invoke a
+ * JVM instance.
+ */
+ public static final String JAVA_COMMAND_PROPERTY = "gwt.jjs.javaCommand";
+
+ /**
+ * A system property that can be used to override the JVM args passed to the
+ * subprocess.
+ */
+ public static final String JVM_ARGS_PROPERTY = "gwt.jjs.javaArgs";
+
+ /**
+ * Launches an external worker and returns the cookie that worker should
+ * return via the network connection.
+ */
+ private static String launchExternalWorker(TreeLogger logger, int port)
+ throws UnableToCompleteException {
+
+ String javaCommand = System.getProperty(JAVA_COMMAND_PROPERTY,
+ System.getProperty("java.home") + File.separator + "bin"
+ + File.separator + "java");
+ logger.log(TreeLogger.TRACE, "javaCommand = " + javaCommand);
+
+ // Construct the arguments
+ List<String> args = new ArrayList<String>();
+ args.add(javaCommand);
+
+ // This will include -Xmx, -D, etc...
+ String userJvmArgs = System.getProperty(JVM_ARGS_PROPERTY);
+ if (userJvmArgs == null) {
+ args.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
+ } else {
+ args.addAll(Arrays.asList(userJvmArgs.split(" ")));
+ }
+
+ // Determine the logLevel for the external program
+ TreeLogger.Type logLevel = TreeLogger.ERROR;
+ for (TreeLogger.Type t : TreeLogger.Type.values()) {
+ if (logger.isLoggable(t)) {
+ logLevel = t;
+ } else {
+ break;
+ }
+ }
+
+ byte[] cookieBytes = new byte[16];
+ (new Random()).nextBytes(cookieBytes);
+ String cookie = Util.toHexString(cookieBytes);
+
+ // Cook up the classpath, main class, and extra args
+ args.addAll(Arrays.asList("-classpath",
+ ManagementFactory.getRuntimeMXBean().getClassPath(),
+ CompilePermsServer.class.getName(), "-host", "localhost", "-port",
+ String.valueOf(port), "-logLevel", logLevel.toString(), "-cookie",
+ cookie));
+
+ // Filter undesirable arguments
+ for (Iterator<String> iter = args.iterator(); iter.hasNext();) {
+ String arg = iter.next();
+ if (arg.startsWith("-agentlib")) {
+ iter.remove();
+ }
+ }
+
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command(args);
+
+ try {
+ final Process proc = builder.start();
+ final BufferedReader bin = new BufferedReader(new InputStreamReader(
+ proc.getInputStream()));
+ final BufferedReader berr = new BufferedReader(new InputStreamReader(
+ proc.getErrorStream()));
+ final TreeLogger procLogger = logger.branch(TreeLogger.DEBUG,
+ "Process output");
+
+ // Threads to copy stdout, stderr to the logger
+ new Thread(new Runnable() {
+ public void run() {
+ while (true) {
+ try {
+ String line = bin.readLine();
+ if (line == null) {
+ break;
+ }
+ procLogger.log(TreeLogger.INFO, line);
+ } catch (EOFException e) {
+ // Ignore
+ } catch (IOException e) {
+ procLogger.log(TreeLogger.ERROR,
+ "Unable to read from subprocess", e);
+ }
+ }
+ }
+ }).start();
+
+ new Thread(new Runnable() {
+ public void run() {
+ while (true) {
+ try {
+ String line = berr.readLine();
+ if (line == null) {
+ break;
+ }
+ procLogger.log(TreeLogger.ERROR, line);
+ } catch (EOFException e) {
+ // Ignore
+ } catch (IOException e) {
+ procLogger.log(TreeLogger.ERROR,
+ "Unable to read from subprocess", e);
+ }
+ }
+ }
+ }).start();
+
+ // The child process should not outlive this JVM
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ public void run() {
+ try {
+ proc.exitValue();
+ } catch (IllegalThreadStateException e) {
+ proc.destroy();
+ }
+ }
+ }));
+
+ return cookie;
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Unable to start external process", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ private ServerSocket sock;
+
+ @Override
+ public Collection<PermutationWorker> getWorkers(TreeLogger logger,
+ UnifiedAst unifiedAst, int numWorkers) throws UnableToCompleteException {
+ File astFile;
+ try {
+ astFile = File.createTempFile("externalPermutationWorkerFactory", ".ser");
+ astFile.deleteOnExit();
+ Util.writeObjectAsFile(logger, astFile, unifiedAst);
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Unable to create temporary file", e);
+ throw new UnableToCompleteException();
+ }
+
+ Set<String> cookies = Collections.synchronizedSet(new HashSet<String>(
+ numWorkers));
+ CountedServerSocket countedSock = new CountedServerSocket(sock, numWorkers);
+ List<PermutationWorker> toReturn = new ArrayList<PermutationWorker>(
+ numWorkers);
+
+ for (int i = 0; i < numWorkers; i++) {
+ String cookie = launchExternalWorker(logger, sock.getLocalPort());
+ cookies.add(cookie);
+ toReturn.add(new ExternalPermutationWorker(countedSock, astFile, cookies));
+ }
+
+ return toReturn;
+ }
+
+ @Override
+ public void init(TreeLogger logger) throws UnableToCompleteException {
+ try {
+ sock = new ServerSocket();
+ /*
+ * Have accept() wait no more than one minute for a connection. This
+ * prevents dead-head behavior.
+ */
+ sock.setSoTimeout(60000);
+ sock.bind(null);
+ logger.log(TreeLogger.SPAM, "Listening for external workers on port "
+ + sock.getLocalPort());
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Unable to create socket", e);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ @Override
+ public boolean isLocal() {
+ return true;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index 3d7b06d..546e5ab 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -17,13 +17,15 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.CompilePerms.CompilePermsOptionsImpl;
import com.google.gwt.dev.CompileTaskRunner.CompileTask;
import com.google.gwt.dev.Link.LinkOptionsImpl;
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.util.PerfLogger;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
+import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
import com.google.gwt.util.tools.Utility;
@@ -44,6 +46,7 @@
registerHandler(new ArgHandlerWorkDirOptional(options));
registerHandler(new ArgHandlerExtraDir(options));
+ registerHandler(new ArgHandlerLocalWorkers(options));
registerHandler(new ArgHandlerOutDir(options));
}
@@ -57,6 +60,7 @@
CompilerOptions {
private LinkOptionsImpl linkOptions = new LinkOptionsImpl();
+ private int localWorkers;
public GWTCompilerOptionsImpl() {
}
@@ -68,12 +72,17 @@
public void copyFrom(CompilerOptions other) {
super.copyFrom(other);
linkOptions.copyFrom(other);
+ localWorkers = other.getLocalWorkers();
}
public File getExtraDir() {
return linkOptions.getExtraDir();
}
+ public int getLocalWorkers() {
+ return localWorkers;
+ }
+
public File getOutDir() {
return linkOptions.getOutDir();
}
@@ -82,6 +91,10 @@
linkOptions.setExtraDir(extraDir);
}
+ public void setLocalWorkers(int localWorkers) {
+ this.localWorkers = localWorkers;
+ }
+
public void setOutDir(File outDir) {
linkOptions.setOutDir(outDir);
}
@@ -117,10 +130,15 @@
}
public boolean run(TreeLogger logger) throws UnableToCompleteException {
+ ModuleDef module = ModuleDefLoader.loadFromClassPath(logger,
+ options.getModuleName());
+
if (options.isValidateOnly()) {
- return new Precompile(options).run(logger);
+ return Precompile.validate(logger, options, module, options.getGenDir(),
+ options.getCompilerWorkDir());
} else {
PerfLogger.start("compile");
+ long compileStart = System.currentTimeMillis();
logger = logger.branch(TreeLogger.INFO, "Compiling module "
+ options.getModuleName());
@@ -131,22 +149,24 @@
tempWorkDir = true;
}
- if (new Precompile(options).run(logger)) {
- /*
- * TODO: use the in-memory result of Precompile to run CompilePerms
- * instead of serializing through the file system.
- */
- CompilePermsOptionsImpl permsOptions = new CompilePermsOptionsImpl();
- permsOptions.copyFrom(options);
- if (new CompilePerms(permsOptions).run(logger)) {
- if (new Link(options).run(logger)) {
- logger.log(TreeLogger.INFO, "Compilation succeeded");
- return true;
- }
- }
- }
+ Precompilation precompilation = Precompile.precompile(logger, options,
+ module, options.getGenDir(), options.getCompilerWorkDir());
- logger.log(TreeLogger.ERROR, "Compilation failed");
+ Permutation[] allPerms = precompilation.getPermutations();
+ File[] resultFiles = CompilePerms.makeResultFiles(
+ options.getCompilerWorkDir(), allPerms);
+ CompilePerms.compile(logger, precompilation, allPerms,
+ options.getLocalWorkers(), resultFiles);
+
+ Link.link(logger.branch(TreeLogger.INFO, "Linking into "
+ + options.getOutDir().getPath()), module, precompilation,
+ resultFiles, options.getOutDir(), options.getExtraDir());
+
+ long compileDone = System.currentTimeMillis();
+ long delta = compileDone - compileStart;
+ logger.log(TreeLogger.INFO, "Compilation succeeded -- "
+ + String.format("%.3f", delta / 1000d) + "s");
+ return true;
} catch (IOException e) {
logger.log(TreeLogger.ERROR,
"Unable to create compiler work directory", e);
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index d476bbc..f10d910 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -100,20 +100,20 @@
}
public static ArtifactSet link(TreeLogger logger, ModuleDef module,
- Precompilation precompilation, File[] jsFiles)
+ Precompilation precompilation, File[] resultFiles)
throws UnableToCompleteException {
StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
module, precompilation.getUnifiedAst().getOptions());
- return doLink(logger, linkerContext, precompilation, jsFiles);
+ return doLink(logger, linkerContext, precompilation, resultFiles);
}
public static void link(TreeLogger logger, ModuleDef module,
- Precompilation precompilation, File[] jsFiles, File outDir, File extrasDir)
- throws UnableToCompleteException {
+ Precompilation precompilation, File[] resultFiles, File outDir,
+ File extrasDir) throws UnableToCompleteException {
StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
module, precompilation.getUnifiedAst().getOptions());
ArtifactSet artifacts = doLink(logger, linkerContext, precompilation,
- jsFiles);
+ resultFiles);
doProduceOutput(logger, artifacts, linkerContext, module, outDir, extrasDir);
}
@@ -142,15 +142,15 @@
private static ArtifactSet doLink(TreeLogger logger,
StandardLinkerContext linkerContext, Precompilation precompilation,
- File[] jsFiles) throws UnableToCompleteException {
+ File[] resultFiles) throws UnableToCompleteException {
Permutation[] perms = precompilation.getPermutations();
- if (perms.length != jsFiles.length) {
+ if (perms.length != resultFiles.length) {
throw new IllegalArgumentException(
- "Mismatched jsFiles.length and permutation count");
+ "Mismatched resultFiles.length and permutation count");
}
for (int i = 0; i < perms.length; ++i) {
- finishPermuation(logger, perms[i], jsFiles[i], linkerContext);
+ finishPermuation(logger, perms[i], resultFiles[i], linkerContext);
}
linkerContext.addOrReplaceArtifacts(precompilation.getGeneratedArtifacts());
@@ -254,11 +254,11 @@
return false;
}
Permutation[] perms = precompilation.getPermutations();
- File[] jsFiles = new File[perms.length];
+ File[] resultFiles = new File[perms.length];
for (int i = 0; i < perms.length; ++i) {
- jsFiles[i] = CompilePerms.makePermFilename(options.getCompilerWorkDir(),
- i);
- if (!jsFiles[i].exists()) {
+ resultFiles[i] = CompilePerms.makePermFilename(
+ options.getCompilerWorkDir(), i);
+ if (!resultFiles[i].exists()) {
logger.log(TreeLogger.ERROR, "File not found '"
+ precompilationFile.getAbsolutePath()
+ "'; please compile all permutations");
@@ -271,7 +271,7 @@
StandardLinkerContext linkerContext = new StandardLinkerContext(branch,
module, precompilation.getUnifiedAst().getOptions());
ArtifactSet artifacts = doLink(branch, linkerContext, precompilation,
- jsFiles);
+ resultFiles);
doProduceOutput(branch, artifacts, linkerContext, module,
options.getOutDir(), options.getExtraDir());
diff --git a/dev/core/src/com/google/gwt/dev/OptionLocalWorkers.java b/dev/core/src/com/google/gwt/dev/OptionLocalWorkers.java
new file mode 100644
index 0000000..e853888
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/OptionLocalWorkers.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Controls the number of local PermutationWorkers to be used.
+ */
+public interface OptionLocalWorkers {
+ /**
+ * Returns the number of workers to run on this machine.
+ */
+ int getLocalWorkers();
+
+ /**
+ * Sets the number of workers to run on this machine.
+ */
+ void setLocalWorkers(int localWorkers);
+}
diff --git a/dev/core/src/com/google/gwt/dev/Permutation.java b/dev/core/src/com/google/gwt/dev/Permutation.java
index 13ca881..3eba3f2 100644
--- a/dev/core/src/com/google/gwt/dev/Permutation.java
+++ b/dev/core/src/com/google/gwt/dev/Permutation.java
@@ -28,13 +28,19 @@
* Represents the state of a single permutation for compile.
*/
public final class Permutation implements Serializable {
+ private final int id;
private final List<StaticPropertyOracle> propertyOracles = new ArrayList<StaticPropertyOracle>();
private final SortedMap<String, String> rebindAnswers = new TreeMap<String, String>();
- public Permutation(StaticPropertyOracle propertyOracle) {
+ public Permutation(int id, StaticPropertyOracle propertyOracle) {
+ this.id = id;
this.propertyOracles.add(propertyOracle);
}
+ public int getId() {
+ return id;
+ }
+
public StaticPropertyOracle[] getPropertyOracles() {
return propertyOracles.toArray(new StaticPropertyOracle[propertyOracles.size()]);
}
diff --git a/dev/core/src/com/google/gwt/dev/PermutationResult.java b/dev/core/src/com/google/gwt/dev/PermutationResult.java
new file mode 100644
index 0000000..0b6f1bf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/PermutationResult.java
@@ -0,0 +1,28 @@
+/*
+ * 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 java.io.Serializable;
+
+/**
+ * An extensible return type for the results of compiling a single permutation.
+ */
+public interface PermutationResult extends Serializable {
+ /**
+ * The compiled JavaScript code.
+ */
+ String[] getJs();
+}
diff --git a/dev/core/src/com/google/gwt/dev/PermutationWorker.java b/dev/core/src/com/google/gwt/dev/PermutationWorker.java
new file mode 100644
index 0000000..f24d35f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/PermutationWorker.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Represents a facility that can compile an individual {@link Permutation}.
+ * Instances of PermutationWorker are expected to be created via
+ * {@link PermutationWorkerFactory#getWorkers(int)}.
+ */
+interface PermutationWorker {
+
+ /**
+ * Compile a single permutation. The {@link com.google.gwt.dev.jjs.UnifiedAst}
+ * will have been provided to {@link PermutationWorkerFactory#getWorkers}
+ * method.
+ *
+ * @throws TransientWorkerException if the Permutation should be tried again
+ * on another worker
+ * @throws UnableToCompleteException due to a fatal error
+ */
+ PermutationResult compile(TreeLogger logger, Permutation permutation)
+ throws TransientWorkerException, UnableToCompleteException;
+
+ /**
+ * Returns a human-readable description of the worker instance. This may be
+ * used for error reporting.
+ */
+ String getName();
+
+ /**
+ * Release any resources associated with the worker.
+ */
+ void shutdown();
+}
diff --git a/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java
new file mode 100644
index 0000000..4448c3d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/PermutationWorkerFactory.java
@@ -0,0 +1,396 @@
+/*
+ * 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.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.util.Util;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Represents a factory for implementations of an endpoint that will invoke
+ * CompilePerms. Implementations of PermutationWorkerFactory should be
+ * default-instantiable and will have {@link #init} called immediately after
+ * construction.
+ */
+public abstract class PermutationWorkerFactory {
+
+ /**
+ * This callable is responsible for compiling one permutation. It waits for an
+ * available worker, uses it, and returns it to the pool when finished.
+ */
+ private static class CompileOnePermutation implements Callable<ResultStatus> {
+ private final BlockingQueue<PermutationWorker> availableWorkers;
+ private final TreeLogger logger;
+ private final Permutation permutation;
+ private final File resultFile;
+
+ public CompileOnePermutation(TreeLogger logger, Permutation permutation,
+ File resultFile, BlockingQueue<PermutationWorker> availableWorkers) {
+ this.logger = logger;
+ this.permutation = permutation;
+ this.resultFile = resultFile;
+ this.availableWorkers = availableWorkers;
+ }
+
+ public ResultStatus call() {
+ // Find a free worker
+ PermutationWorker worker;
+ try {
+ worker = availableWorkers.take();
+ } catch (InterruptedException e) {
+ logger.log(TreeLogger.DEBUG, "Worker interrupted", e);
+ return ResultStatus.HARD_FAILURE;
+ }
+
+ if (worker == noMoreWorkersWorker) {
+ // Shutting down
+ return ResultStatus.HARD_FAILURE;
+ }
+
+ // Invoke the worker
+ try {
+ PermutationResult result = worker.compile(logger, permutation);
+ Util.writeObjectAsFile(logger, resultFile, result);
+ logger.log(TreeLogger.DEBUG, "Successfully compiled permutation");
+ availableWorkers.add(worker);
+ return ResultStatus.SUCCESS;
+ } catch (TransientWorkerException e) {
+ logger.log(TreeLogger.DEBUG, "Worker died, will retry Permutation", e);
+ return ResultStatus.TRANSIENT_FAILURE;
+ } catch (UnableToCompleteException e) {
+ logger.log(TreeLogger.ERROR, "Unrecoverable exception, shutting down",
+ e);
+ return ResultStatus.HARD_FAILURE;
+ }
+ }
+ }
+
+ private static enum ResultStatus {
+ /**
+ * A failure bad enough to merit shutting down the compilation.
+ */
+ HARD_FAILURE,
+
+ /**
+ * A successful compile.
+ */
+ SUCCESS,
+
+ /**
+ * A worker died while processing this permutation, but it is worth trying
+ * to compile it with another worker.
+ */
+ TRANSIENT_FAILURE
+ };
+
+ /**
+ * The name of the system property used to define the workers.
+ */
+ public static final String FACTORY_IMPL_PROPERTY = "gwt.jjs.permutationWorkerFactory";
+
+ /**
+ * This value can be passed into {@link #setLocalWorkers(int)} to indicate
+ * that a heuristic should be used to determine the total number of local
+ * workers.
+ */
+ public static final int WORKERS_AUTO = 0;
+
+ private static List<PermutationWorkerFactory> lazyFactories;
+
+ private static final PermutationWorker noMoreWorkersWorker = new PermutationWorker() {
+
+ public PermutationResult compile(TreeLogger logger, Permutation permutation)
+ throws TransientWorkerException, UnableToCompleteException {
+ throw new UnableToCompleteException();
+ }
+
+ public String getName() {
+ return "Marker worker indicating no more workers";
+ }
+
+ public void shutdown() {
+ }
+ };
+
+ /**
+ * Compiles all Permutations in a Precompilation and returns an array of Files
+ * that can be consumed by Link using the system-default
+ * PermutationWorkersFactories.
+ */
+ public static void compilePermutations(TreeLogger logger,
+ Precompilation precompilation, int localWorkers, File[] resultFiles)
+ throws UnableToCompleteException {
+ compilePermutations(logger, precompilation,
+ precompilation.getPermutations(), localWorkers, resultFiles);
+ }
+
+ /**
+ * Compiles a subset of the Permutations in a Precompilation and returns an
+ * array of Files that can be consumed by Link using the system-default
+ * PermutationWorkersFactories.
+ *
+ * @param localWorkers Set the maximum number of workers that should be
+ * executed on the local system by the PermutationWorkerFactory. The
+ * value {@link #WORKERS_AUTO} will allow the
+ * PermutationWorkerFactory to apply a heuristic to determine the
+ * correct number of local workers.
+ * @param resultFiles the output files to write into; must be the same length
+ * as permutations
+ */
+ public static void compilePermutations(TreeLogger logger,
+ Precompilation precompilation, Permutation[] permutations,
+ int localWorkers, File[] resultFiles) throws UnableToCompleteException {
+ assert permutations.length == resultFiles.length;
+ assert Arrays.asList(precompilation.getPermutations()).containsAll(
+ Arrays.asList(permutations));
+
+ // We may have a mixed collection of workers from different factories
+ List<PermutationWorker> workers = new ArrayList<PermutationWorker>();
+
+ /*
+ * We can have errors below this point, there's a finally block to handle
+ * cleanup of workers.
+ */
+ try {
+ createWorkers(logger, precompilation.getUnifiedAst(),
+ permutations.length, localWorkers, workers);
+ ExecutorService executor = Executors.newFixedThreadPool(workers.size());
+
+ // List of available workers.
+ // The extra space is for inserting nulls at shutdown time
+ BlockingQueue<PermutationWorker> availableWorkers = new ArrayBlockingQueue<PermutationWorker>(
+ 2 * workers.size());
+ availableWorkers.addAll(workers);
+
+ try {
+
+ // Submit all tasks to the executor
+
+ // The permutation compiles not yet finished
+ Queue<CompileOnePermutation> tasksOutstanding = new LinkedList<CompileOnePermutation>();
+
+ // The futures for the results of those compiles
+ Queue<Future<ResultStatus>> resultFutures = new LinkedList<Future<ResultStatus>>();
+
+ for (int i = 0; i < permutations.length; ++i) {
+ TreeLogger permLogger = logger.branch(TreeLogger.DEBUG,
+ "Worker permutation " + permutations[i].getId() + " of "
+ + permutations.length);
+ CompileOnePermutation task = new CompileOnePermutation(permLogger,
+ permutations[i], resultFiles[i], availableWorkers);
+ tasksOutstanding.add(task);
+ resultFutures.add(executor.submit(task));
+ }
+
+ // Count the number of dead workers
+ int numDeadWorkers = 0;
+ int successCount = 0;
+
+ while (!resultFutures.isEmpty() && numDeadWorkers < workers.size()) {
+ assert resultFutures.size() == tasksOutstanding.size();
+
+ CompileOnePermutation task = tasksOutstanding.remove();
+ Future<ResultStatus> future = resultFutures.remove();
+ ResultStatus result;
+ try {
+ result = future.get();
+ } catch (InterruptedException e) {
+ logger.log(TreeLogger.ERROR,
+ "Exiting without results due to interruption", e);
+ throw new UnableToCompleteException();
+ } catch (ExecutionException e) {
+ logger.log(TreeLogger.ERROR, "A compilation failed", e);
+ throw new UnableToCompleteException();
+ }
+
+ if (result == ResultStatus.SUCCESS) {
+ ++successCount;
+ } else if (result == ResultStatus.TRANSIENT_FAILURE) {
+ // A worker died. Resubmit for the remaining workers.
+ ++numDeadWorkers;
+ tasksOutstanding.add(task);
+ resultFutures.add(executor.submit(task));
+ } else if (result == ResultStatus.HARD_FAILURE) {
+ // Shut down.
+ break;
+ } else {
+ throw new InternalCompilerException("Unknown result type");
+ }
+ }
+
+ // Too many permutations is a coding error
+ assert successCount <= permutations.length;
+
+ if (successCount < permutations.length) {
+ // Likely as not, all of the workers died
+ logger.log(TreeLogger.ERROR, "Not all permutation were compiled "
+ + successCount + " of " + permutations.length);
+ throw new UnableToCompleteException();
+ }
+ } finally {
+ // Shut down the executor
+ executor.shutdown();
+
+ // Inform any residual CompileOnePermutation's that there aren't any
+ // more workers
+ for (int i = 0; i < workers.size(); i++) {
+ availableWorkers.add(noMoreWorkersWorker);
+ }
+ }
+ } finally {
+ // Shut down all workers
+ for (PermutationWorker worker : workers) {
+ worker.shutdown();
+ }
+ }
+ }
+
+ /**
+ * Creates one or more implementations of worker factories. This will treat
+ * the value of the {@value #FACTORY_IMPL_PROPERTY} system property as a
+ * comma-separated list of type names.
+ */
+ private static synchronized List<PermutationWorkerFactory> createAll(
+ TreeLogger logger) throws UnableToCompleteException {
+ // NB: This is the much-derided FactoryFactory pattern
+
+ logger = logger.branch(TreeLogger.TRACE,
+ "Creating PermutationWorkerFactory instances");
+
+ if (lazyFactories != null) {
+ logger.log(TreeLogger.SPAM, "Using lazy instances");
+ return lazyFactories;
+ }
+
+ List<PermutationWorkerFactory> mutableFactories = new ArrayList<PermutationWorkerFactory>();
+ String classes = System.getProperty(FACTORY_IMPL_PROPERTY,
+ ThreadedPermutationWorkerFactory.class.getName() + ","
+ + ExternalPermutationWorkerFactory.class.getName());
+ logger.log(TreeLogger.SPAM, "Factory impl property is " + classes);
+
+ String[] classParts = classes.split(",");
+ for (String className : classParts) {
+ try {
+ Class<? extends PermutationWorkerFactory> clazz = Class.forName(
+ className).asSubclass(PermutationWorkerFactory.class);
+ PermutationWorkerFactory factory = clazz.newInstance();
+ factory.init(logger);
+ mutableFactories.add(factory);
+ logger.log(TreeLogger.SPAM, "Added PermutationWorkerFactory "
+ + clazz.getName());
+ } catch (ClassCastException e) {
+ logger.log(TreeLogger.ERROR, className + " is not a "
+ + PermutationWorkerFactory.class.getName());
+ } catch (ClassNotFoundException e) {
+ logger.log(TreeLogger.ERROR,
+ "Unable to find PermutationWorkerFactory named " + className);
+ } catch (InstantiationException e) {
+ logger.log(TreeLogger.ERROR,
+ "Unable to instantiate PermutationWorkerFactory " + className, e);
+ } catch (IllegalAccessException e) {
+ logger.log(TreeLogger.ERROR,
+ "Unable to instantiate PermutationWorkerFactory " + className, e);
+ }
+ }
+
+ if (mutableFactories.size() == 0) {
+ logger.log(TreeLogger.ERROR,
+ "No usable PermutationWorkerFactories available");
+ throw new UnableToCompleteException();
+ }
+
+ return lazyFactories = Collections.unmodifiableList(mutableFactories);
+ }
+
+ /**
+ * Create as many workers as possible to service the Permutations.
+ */
+ private static void createWorkers(TreeLogger logger, UnifiedAst unifiedAst,
+ int workersNeeded, int localWorkers, List<PermutationWorker> workers)
+ throws UnableToCompleteException {
+ if (localWorkers <= WORKERS_AUTO) {
+ // TODO: something smarter?
+ localWorkers = 1;
+ }
+
+ for (PermutationWorkerFactory factory : PermutationWorkerFactory.createAll(logger)) {
+ if (workersNeeded <= 0) {
+ break;
+ }
+
+ int wanted = factory.isLocal() ? Math.min(workersNeeded, localWorkers)
+ : workersNeeded;
+ if (wanted <= 0) {
+ continue;
+ }
+
+ Collection<PermutationWorker> newWorkers = factory.getWorkers(logger,
+ unifiedAst, wanted);
+
+ workers.addAll(newWorkers);
+ workersNeeded -= newWorkers.size();
+ if (factory.isLocal()) {
+ localWorkers -= newWorkers.size();
+ }
+ }
+
+ if (workers.size() == 0) {
+ logger.log(TreeLogger.ERROR, "No PermutationWorkers created");
+ throw new UnableToCompleteException();
+ }
+ }
+
+ /**
+ * Return some number of PermutationWorkers.
+ *
+ * @param unifiedAst a UnifiedAst
+ * @param numWorkers the desired number of workers
+ * @return a collection of PermutationWorkers, the size of which may be less
+ * than <code>numWorkers</code>
+ */
+ public abstract Collection<PermutationWorker> getWorkers(TreeLogger logger,
+ UnifiedAst unifiedAst, int numWorkers) throws UnableToCompleteException;
+
+ /**
+ * Initialize the PermutationWorkerFactory.
+ */
+ public abstract void init(TreeLogger logger) throws UnableToCompleteException;
+
+ /**
+ * Indicates if the PermutationWorkers created by the factory consume
+ * computational or memory resources on the local system, as opposed to the
+ * per-permutation work being performed on a remote system.
+ */
+ public abstract boolean isLocal();
+}
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index a7e8bf0..c5a4d82 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -171,7 +171,7 @@
rebindOracles[i] = new StandardRebindOracle(compilationState,
propertyOracles[i], module, rules, genDir, generatorResourcesDir,
generatorArtifacts);
- permutations[i] = new Permutation(propertyOracles[i]);
+ permutations[i] = new Permutation(i, propertyOracles[i]);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java b/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
new file mode 100644
index 0000000..89eebf0
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/ThreadedPermutationWorkerFactory.java
@@ -0,0 +1,103 @@
+/*
+ * 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.dev.jjs.UnifiedAst;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Create a single in-process PermutationWorker. This WorkerFactory is intended
+ * to be used as a fall-back in case the other PermutationWorkers are unable to
+ * complete.
+ */
+public class ThreadedPermutationWorkerFactory extends PermutationWorkerFactory {
+
+ private static class ThreadedPermutationWorker implements PermutationWorker {
+ private final UnifiedAst ast;
+ private final int id;
+
+ public ThreadedPermutationWorker(UnifiedAst ast, int id) {
+ this.ast = ast;
+ this.id = id;
+ }
+
+ public PermutationResult compile(final TreeLogger logger,
+ final Permutation permutation) throws TransientWorkerException,
+ UnableToCompleteException {
+ try {
+ return CompilePerms.compile(logger, permutation, ast);
+ } catch (OutOfMemoryError e) {
+ logger.log(TreeLogger.ERROR,
+ "OutOfMemoryError: Increase heap size or lower "
+ + MAX_THREADS_PROPERTY);
+ throw new UnableToCompleteException();
+ }
+ }
+
+ public String getName() {
+ return "In-process PermutationWorker " + id;
+ }
+
+ public void shutdown() {
+ // No-op
+ }
+ }
+
+ /**
+ * A Java system property that can be used to change the number of in-process
+ * threads used.
+ */
+ public static final String MAX_THREADS_PROPERTY = "gwt.jjs.maxThreads";
+
+ @Override
+ public Collection<PermutationWorker> getWorkers(TreeLogger logger,
+ UnifiedAst unifiedAst, int numWorkers) throws UnableToCompleteException {
+ logger = logger.branch(TreeLogger.SPAM,
+ "Creating ThreadedPermutationWorkers");
+
+ numWorkers = Math.min(numWorkers, Integer.getInteger(MAX_THREADS_PROPERTY,
+ 1));
+
+ if (numWorkers == 0) {
+ return Collections.emptyList();
+ }
+
+ // The worker will deserialize a new copy
+ List<PermutationWorker> toReturn = new ArrayList<PermutationWorker>(
+ numWorkers);
+ for (int i = 0; i < numWorkers; i++) {
+ toReturn.add(new ThreadedPermutationWorker(unifiedAst, i));
+ }
+ return toReturn;
+ }
+
+ @Override
+ public void init(TreeLogger logger) throws UnableToCompleteException {
+ logger = logger.branch(TreeLogger.SPAM,
+ "Initializing ThreadedPermutationWorkerFactory");
+ }
+
+ @Override
+ public boolean isLocal() {
+ return true;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/TransientWorkerException.java b/dev/core/src/com/google/gwt/dev/TransientWorkerException.java
new file mode 100644
index 0000000..5f4e2bc
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/TransientWorkerException.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Indicates that a PermutationWorker failed to complete its assignment, but
+ * that the permutation should be retried on another worker. This would be used
+ * to report errors such as OutOfMemory when compiling in-process or unexpected
+ * death of an out-of-process worker (network break, etc).
+ */
+public class TransientWorkerException extends Exception {
+ public TransientWorkerException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
index 6dfc5fe..d0d2e6d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
@@ -143,6 +143,18 @@
return rebindRequests;
}
+ /**
+ * Internally prepares a new AST for compilation if one is not already
+ * prepared.
+ */
+ public void prepare() {
+ synchronized (myLockObject) {
+ if (initialAst == null) {
+ initialAst = deserializeAst(serializedAst);
+ }
+ }
+ }
+
AST getFreshAst() {
synchronized (myLockObject) {
if (initialAst != null) {
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 31f6972..53e5f0c 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -311,8 +311,7 @@
}
/**
- * Returns a String as a byte-array using the default encoding for the
- * compiler.
+ * Returns a byte-array representing the default encoding for a String.
*/
public static byte[] getBytes(String s) {
try {
@@ -323,6 +322,10 @@
}
}
+ /**
+ * Returns an array of byte-arrays representing the default encoding for an
+ * array of Strings.
+ */
public static byte[][] getBytes(String[] s) {
byte[][] bytes = new byte[s.length][];
for (int i = 0; i < s.length; i++) {
@@ -578,24 +581,24 @@
logger.log(TreeLogger.INFO, "Unable to dump source to disk", caught);
}
- public static byte[][] readFileAndSplit(File file) {
- RandomAccessFile f = null;
- try {
- f = new RandomAccessFile(file, "r");
- int length = f.readInt();
- byte[][] results = new byte[length][];
- for (int i = 0; i < length; i++) {
- int nextLength = f.readInt();
- results[i] = new byte[nextLength];
- f.read(results[i]);
- }
- return results;
- } catch (IOException e) {
- return null;
- } finally {
- Utility.close(f);
- }
- }
+ // public static byte[][] readFileAndSplit(File file) {
+ // RandomAccessFile f = null;
+ // try {
+ // f = new RandomAccessFile(file, "r");
+ // int length = f.readInt();
+ // byte[][] results = new byte[length][];
+ // for (int i = 0; i < length; i++) {
+ // int nextLength = f.readInt();
+ // results[i] = new byte[nextLength];
+ // f.read(results[i]);
+ // }
+ // return results;
+ // } catch (IOException e) {
+ // return null;
+ // } finally {
+ // Utility.close(f);
+ // }
+ // }
public static byte[] readFileAsBytes(File file) {
FileInputStream fileInputStream = null;
@@ -1155,30 +1158,31 @@
}
}
- /**
- * Write all of the supplied bytes to the file, in a way that they can be read
- * back by {@link #readFileAndSplit(File).
- */
- public static boolean writeStringsAsFile(TreeLogger branch,
- File makePermFilename, String[] js) {
- RandomAccessFile f = null;
- try {
- makePermFilename.delete();
- makePermFilename.getParentFile().mkdirs();
- f = new RandomAccessFile(makePermFilename, "rwd");
- f.writeInt(js.length);
- for (String s : js) {
- byte[] b = getBytes(s);
- f.writeInt(b.length);
- f.write(b);
- }
- return true;
- } catch (IOException e) {
- return false;
- } finally {
- Utility.close(f);
- }
- }
+ // /**
+ // * Write all of the supplied bytes to the file, in a way that they can be
+ // read
+ // * back by {@link #readFileAndSplit(File).
+ // */
+ // public static boolean writeStringsAsFile(TreeLogger branch,
+ // File makePermFilename, String[] js) {
+ // RandomAccessFile f = null;
+ // try {
+ // makePermFilename.delete();
+ // makePermFilename.getParentFile().mkdirs();
+ // f = new RandomAccessFile(makePermFilename, "rwd");
+ // f.writeInt(js.length);
+ // for (String s : js) {
+ // byte[] b = getBytes(s);
+ // f.writeInt(b.length);
+ // f.write(b);
+ // }
+ // return true;
+ // } catch (IOException e) {
+ // return false;
+ // } finally {
+ // Utility.close(f);
+ // }
+ // }
/**
* Reads the specified number of bytes from the {@link InputStream}.
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLocalWorkers.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLocalWorkers.java
new file mode 100644
index 0000000..6800c83
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLocalWorkers.java
@@ -0,0 +1,58 @@
+/*
+ * 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.util.arg;
+
+import com.google.gwt.dev.OptionLocalWorkers;
+import com.google.gwt.util.tools.ArgHandlerInt;
+
+/**
+ * An arg handler to specify the number of local workers that should be used by
+ * the compiler.
+ */
+public class ArgHandlerLocalWorkers extends ArgHandlerInt {
+
+ private final OptionLocalWorkers options;
+
+ public ArgHandlerLocalWorkers(OptionLocalWorkers options) {
+ this.options = options;
+ }
+
+ @Override
+ public String[] getDefaultArgs() {
+ // Default to 1 for now; we might do an "auto" later.
+ return new String[] {getTag(), "1"};
+ }
+
+ @Override
+ public String getPurpose() {
+ return "Specifies the number of local workers to use whe compiling permutations";
+ }
+
+ @Override
+ public String getTag() {
+ return "-localWorkers";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"count"};
+ }
+
+ @Override
+ public void setInt(int value) {
+ options.setLocalWorkers(value);
+ }
+}
diff --git a/reference/dispatch/Dispatch.gwt.xml b/reference/dispatch/Dispatch.gwt.xml
new file mode 100644
index 0000000..fa1c708
--- /dev/null
+++ b/reference/dispatch/Dispatch.gwt.xml
@@ -0,0 +1,6 @@
+<!DOCTYPE document SYSTEM
+ "http://google-web-toolkit.googlecode.com/svn/releases/1.5/distro-source/core/src/gwt-module.dtd">
+<module>
+ <inherits name='com.google.gwt.user.User' />
+ <entry-point class='kellegous.client.Dispatch' />
+</module>
diff --git a/reference/dispatch/client/Dispatch.java b/reference/dispatch/client/Dispatch.java
new file mode 100644
index 0000000..cac6f18
--- /dev/null
+++ b/reference/dispatch/client/Dispatch.java
@@ -0,0 +1,143 @@
+package kellegous.client;
+
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
+
+public class Dispatch implements EntryPoint {
+ private static native Event createMockEvent() /*-{
+ return { type: "click" };
+ }-*/;
+
+ private static double[] runTrial(int numberOfObservers,
+ int numberOfIterations, int numberOfSamples) {
+ final double[] results = new double[numberOfSamples];
+ final EventListener subject = Subject.create(numberOfObservers);
+ final Event event = createMockEvent();
+ for (int j = 0; j < numberOfSamples; ++j) {
+ final Duration d = new Duration();
+ for (int i = 0; i < numberOfIterations; ++i) {
+ subject.onBrowserEvent(event);
+ }
+ results[j] = d.elapsedMillis();
+ }
+ return results;
+ }
+
+ private static native void schedule(Command command) /*-{
+ $wnd.setTimeout(function() {
+ command.@com.google.gwt.user.client.Command::execute()();
+ }, 0);
+ }-*/;
+
+ // t-distribution for p = 0.05 (used to compute 95% confidence intervals).
+ // This table is based at df = 2.
+ private final static double[] TDIST = new double[] {
+ 4.3027, 3.1824, 2.7765, 2.5706, 2.4469, 2.3646, 2.3060, 2.2622, 2.2281,
+ 2.2010, 2.1788, 2.1604, 2.1448, 2.1315, 2.1199, 2.1098, 2.1009, 2.0930,
+ 2.0860, 2.0796, 2.0739, 2.0687, 2.0639, 2.0595, 2.0555, 2.0518, 2.0484,
+ 2.0452, 2.0423, 2.0395, 2.0369, 2.0345, 2.0322, 2.0301, 2.0281, 2.0262,
+ 2.0244, 2.0227, 2.0211, 2.0195, 2.0181, 2.0167, 2.0154, 2.0141, 2.0129,
+ 2.0117, 2.0106, 2.0096, 2.0086, 2.0076, 2.0066, 2.0057, 2.0049, 2.0040,
+ 2.0032, 2.0025, 2.0017, 2.0010, 2.0003, 1.9996, 1.9990, 1.9983, 1.9977,
+ 1.9971, 1.9966, 1.9960, 1.9955, 1.9949, 1.9944, 1.9939, 1.9935, 1.9930,
+ 1.9925, 1.9921, 1.9917, 1.9913, 1.9908, 1.9905, 1.9901, 1.9897, 1.9893,
+ 1.9890, 1.9886, 1.9883, 1.9879, 1.9876, 1.9873, 1.9870, 1.9867, 1.9864,
+ 1.9861, 1.9858, 1.9855, 1.9852, 1.9850, 1.9847, 1.9845, 1.9842, 1.9840};
+
+ private static double computeT(int df) {
+ return TDIST[df - 2];
+ }
+
+ private static double computeMean(double[] s) {
+ double sum = 0.0;
+ final int n = s.length;
+ for (int i = 0; i < n; ++i) {
+ sum += s[i];
+ }
+ return sum / n;
+ }
+
+ private static double computeStandardError(double[] data, double mean) {
+ final int n = data.length;
+ double sum = 0.0;
+ for (int i = 0; i < n; ++i) {
+ final double d = data[i] - mean;
+ sum += d * d;
+ }
+
+ return Math.sqrt(sum / n) / Math.sqrt(n);
+ }
+
+ private static class Stats {
+ private final double mean, upper, lower;
+
+ Stats(double[] data) {
+ mean = computeMean(data);
+ final double error = computeStandardError(data, mean);
+ final double t = computeT(data.length - 1);
+ upper = mean + t * error;
+ lower = mean - t * error;
+ }
+
+ @Override
+ public String toString() {
+ return mean + ", " + lower + ", " + upper;
+ }
+ }
+
+ public static class Runner {
+ private int n;
+ private final int max, numIterations, numSamples;
+ private Stats[] results;
+
+ Runner(int min, int max, int numIterations, int numSamples) {
+ n = min;
+ this.max = max;
+ this.numIterations = numIterations;
+ this.numSamples = numSamples;
+ results = new Stats[max - min + 1];
+ }
+
+ void finish() {
+ final Document document = Document.get();
+ final DivElement root = document.createDivElement();
+ document.getBody().appendChild(root);
+ for (int i = 0, n = results.length; i < n; ++i) {
+ final DivElement div = document.createDivElement();
+ root.appendChild(div);
+ div.setInnerText("" + results[i].toString());
+ }
+ }
+
+ void next() {
+ schedule(new Command() {
+ public void execute() {
+ final double[] results = runTrial(n, numIterations, numSamples);
+ Runner.this.results[n] = new Stats(results);
+ if (++n <= max) {
+ next();
+ } else {
+ finish();
+ }
+ }
+ });
+ }
+
+ void run() {
+ next();
+ }
+ }
+
+ public void onModuleLoad() {
+ // Don't run this in hosted mode.
+ if (GWT.isScript()) {
+ new Runner(0, 10, 10000, 20).run();
+ }
+ }
+}
diff --git a/reference/dispatch/client/Subject.java b/reference/dispatch/client/Subject.java
new file mode 100644
index 0000000..fc21bee
--- /dev/null
+++ b/reference/dispatch/client/Subject.java
@@ -0,0 +1,22 @@
+package kellegous.client;
+
+import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Widget;
+
+public class Subject {
+ public static EventListener create(int numberOfObserver) {
+ final Button subject = new Button("a button");
+ for (int i = 0; i < numberOfObserver; ++i) {
+ subject.addClickListener(new ClickListener() {
+ private int count = 0;
+
+ public void onClick(Widget sender) {
+ count++;
+ }
+ });
+ }
+ return subject;
+ }
+}
diff --git a/reference/dispatch/public/Dispatch.html b/reference/dispatch/public/Dispatch.html
new file mode 100644
index 0000000..e87c244
--- /dev/null
+++ b/reference/dispatch/public/Dispatch.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>Dispatch5</title>
+ <script type="text/javascript" language="javascript" src="kellegous.Dispatch.nocache.js"></script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
index 9d656ad..a4378e3 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
@@ -28,9 +28,9 @@
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.LazyPanel;
import com.google.gwt.user.client.ui.TabBar;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -59,7 +59,7 @@
* <li>.sc-ContentWidget-description { Applied to the description }</li>
* </ul>
*/
-public abstract class ContentWidget extends Composite implements
+public abstract class ContentWidget extends LazyPanel implements
SelectionHandler<Integer> {
/**
* The constants used in this Content Widget.
@@ -90,7 +90,7 @@
/**
* An instance of the constants.
*/
- private CwConstants constants;
+ private final CwConstants constants;
/**
* The deck panel with the contents.
@@ -98,11 +98,6 @@
private DeckPanel deckPanel = null;
/**
- * A boolean indicating whether or not this widget has been initialized.
- */
- private boolean initialized = false;
-
- /**
* A boolean indicating whether or not the RPC request for the source code has
* been sent.
*/
@@ -131,13 +126,11 @@
public ContentWidget(CwConstants constants) {
this.constants = constants;
tabBar = new TabBar();
- deckPanel = new DeckPanel();
- initWidget(deckPanel);
- setStyleName(DEFAULT_STYLE_NAME);
}
/**
- * Add an item to this content widget.
+ * Add an item to this content widget. Should not be called before
+ * {@link #onInitializeComplete} has been called.
*
* @param w the widget to add
* @param tabText the text to display in the tab
@@ -187,66 +180,8 @@
}
/**
- * Initialize this widget by creating the elements that should be added to the
- * page.
- */
- public final void initialize() {
- if (initialized == false) {
- initialized = true;
-
- // Add a tab handler
- tabBar.addSelectionHandler(this);
-
- // Create a container for the main example
- final VerticalPanel vPanel = new VerticalPanel();
- add(vPanel, constants.contentWidgetExample());
-
- // Add the name
- HTML nameWidget = new HTML(getName());
- nameWidget.setStyleName(DEFAULT_STYLE_NAME + "-name");
- vPanel.add(nameWidget);
-
- // Add the description
- HTML descWidget = new HTML(getDescription());
- descWidget.setStyleName(DEFAULT_STYLE_NAME + "-description");
- vPanel.add(descWidget);
-
- // Add source code tab
- if (hasSource()) {
- sourceWidget = new HTML();
- add(sourceWidget, constants.contentWidgetSource());
- } else {
- sourceLoaded = true;
- }
-
- // Add style tab
- if (hasStyle()) {
- styleDefs = new HashMap<String, String>();
- styleWidget = new HTML();
- add(styleWidget, constants.contentWidgetStyle());
- }
-
- asyncOnInitialize(new AsyncCallback<Widget>() {
-
- public void onFailure(Throwable caught) {
- Window.alert("exception: " + caught);
- }
-
- public void onSuccess(Widget result) {
- // Initialize the widget and add it to the page
- Widget widget = result;
- if (widget != null) {
- vPanel.add(widget);
- }
- onInitializeComplete();
- }
- });
- }
- }
-
- /**
- * When the widget is first initialize, this method is called. If it returns a
- * Widget, the widget will be added as the first tab. Return null to disable
+ * When the widget is first initialized, this method is called. If it returns
+ * a Widget, the widget will be added as the first tab. Return null to disable
* the first tab.
*
* @return the widget to add to the first tab
@@ -315,10 +250,69 @@
protected abstract void asyncOnInitialize(final AsyncCallback<Widget> callback);
+ /**
+ * Initialize this widget by creating the elements that should be added to the
+ * page.
+ */
+ protected final Widget createWidget() {
+ deckPanel = new DeckPanel();
+
+ setStyleName(DEFAULT_STYLE_NAME);
+
+ // Add a tab handler
+ tabBar.addSelectionHandler(this);
+
+ // Create a container for the main example
+ final VerticalPanel vPanel = new VerticalPanel();
+ add(vPanel, constants.contentWidgetExample());
+
+ // Add the name
+ HTML nameWidget = new HTML(getName());
+ nameWidget.setStyleName(DEFAULT_STYLE_NAME + "-name");
+ vPanel.add(nameWidget);
+
+ // Add the description
+ HTML descWidget = new HTML(getDescription());
+ descWidget.setStyleName(DEFAULT_STYLE_NAME + "-description");
+ vPanel.add(descWidget);
+
+ // Add source code tab
+ if (hasSource()) {
+ sourceWidget = new HTML();
+ add(sourceWidget, constants.contentWidgetSource());
+ } else {
+ sourceLoaded = true;
+ }
+
+ // Add style tab
+ if (hasStyle()) {
+ styleDefs = new HashMap<String, String>();
+ styleWidget = new HTML();
+ add(styleWidget, constants.contentWidgetStyle());
+ }
+
+ asyncOnInitialize(new AsyncCallback<Widget>() {
+
+ public void onFailure(Throwable caught) {
+ Window.alert("exception: " + caught);
+ }
+
+ public void onSuccess(Widget result) {
+ // Initialize the showcase widget (if any) and add it to the page
+ Widget widget = result;
+ if (widget != null) {
+ vPanel.add(widget);
+ }
+ onInitializeComplete();
+ }
+ });
+
+ return deckPanel;
+ }
+
@Override
protected void onLoad() {
- // Initialize this widget if we haven't already
- initialize();
+ ensureWidget();
// Select the first tab
if (getTabBar().getTabCount() > 0) {
diff --git a/user/javadoc/com/google/gwt/examples/LazyPanelExample.java b/user/javadoc/com/google/gwt/examples/LazyPanelExample.java
new file mode 100644
index 0000000..6dead55
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/LazyPanelExample.java
@@ -0,0 +1,54 @@
+/*
+ * 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.examples;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.LazyPanel;
+import com.google.gwt.user.client.ui.PushButton;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class LazyPanelExample implements EntryPoint {
+
+ /**
+ * A friendly little LazyPanel.
+ */
+ static class HelloLazy extends LazyPanel {
+ @Override
+ protected Widget createWidget() {
+ return new Label("Well hello there!");
+ }
+ }
+
+ public void onModuleLoad() {
+ final Widget lazy = new HelloLazy();
+ lazy.setVisible(false);
+
+ PushButton b = new PushButton("Click me");
+ b.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ lazy.setVisible(true);
+ }
+ });
+
+ RootPanel root = RootPanel.get();
+ root.add(b);
+ root.add(lazy);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java b/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
index 12ee898..32e6be8 100644
--- a/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
@@ -89,6 +89,10 @@
run(ANIMATION_DURATION);
} else {
panel.contentWrapper.setVisible(panel.isOpen);
+ if (panel.isOpen) {
+ // Special treatment on the visible case to ensure LazyPanel works
+ panel.getContent().setVisible(true);
+ }
}
}
@@ -107,7 +111,9 @@
super.onStart();
if (opening) {
curPanel.contentWrapper.setVisible(true);
- }
+ // Special treatment on the visible case to ensure LazyPanel works
+ curPanel.getContent().setVisible(true);
+ }
}
@Override
diff --git a/user/src/com/google/gwt/user/client/ui/LazyPanel.java b/user/src/com/google/gwt/user/client/ui/LazyPanel.java
new file mode 100644
index 0000000..5aca1a2
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/LazyPanel.java
@@ -0,0 +1,68 @@
+/*
+ * 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.user.client.ui;
+
+/**
+ * Convenience class to help lazy loading. The bulk of a LazyPanel is not
+ * instantiated until {@link #setVisible}(true) or {@link #ensureWidget} is
+ * called.
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.LazyPanelExample}
+ */
+public abstract class LazyPanel extends SimplePanel {
+
+ public LazyPanel() {
+ }
+
+ /**
+ * Create the widget contained within the {@link LazyPanel}.
+ *
+ * @return the lazy widget
+ */
+ protected abstract Widget createWidget();
+
+ /**
+ * Ensures that the widget has been created by calling {@link #createWidget}
+ * if {@link #getWidget} returns <code>null</code>. Typically it is not
+ * necessary to call this directly, as it is called as a side effect of a
+ * <code>setVisible(true)</code> call.
+ */
+ public void ensureWidget() {
+ Widget widget = getWidget();
+ if (widget == null) {
+ widget = createWidget();
+ setWidget(widget);
+ }
+ }
+
+ @Override
+ /**
+ * Sets whether this object is visible. If <code>visible</code> is
+ * <code>true</code>, creates the sole child widget if necessary by calling
+ * {@link #ensureWidget}.
+ *
+ * @param visible <code>true</code> to show the object, <code>false</code>
+ * to hide it
+ */
+ public void setVisible(boolean visible) {
+ if (visible) {
+ ensureWidget();
+ }
+ super.setVisible(visible);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/TabPanel.java b/user/src/com/google/gwt/user/client/ui/TabPanel.java
index e15f772..2203703 100644
--- a/user/src/com/google/gwt/user/client/ui/TabPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/TabPanel.java
@@ -42,9 +42,12 @@
* {@link com.google.gwt.user.client.ui.HasWidgets}.
* </p>
*
- * <h3>CSS Style Rules</h3> <ul class='css'> <li>.gwt-TabPanel { the tab panel
- * itself }</li> <li>.gwt-TabPanelBottom { the bottom section of the tab panel
- * (the deck containing the widget) }</li> </ul>
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-TabPanel { the tab panel itself }</li>
+ * <li>.gwt-TabPanelBottom { the bottom section of the tab panel
+ * (the deck containing the widget) }</li>
+ * </ul>
*
* <p>
* <h3>Example</h3>
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 1452667..1f06f51 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -47,6 +47,7 @@
import com.google.gwt.user.client.ui.HorizontalPanelTest;
import com.google.gwt.user.client.ui.HyperlinkTest;
import com.google.gwt.user.client.ui.ImageTest;
+import com.google.gwt.user.client.ui.LazyPanelTest;
import com.google.gwt.user.client.ui.LinearPanelTest;
import com.google.gwt.user.client.ui.ListBoxTest;
import com.google.gwt.user.client.ui.MenuBarTest;
@@ -78,7 +79,7 @@
import junit.framework.Test;
/**
- * TODO: document me.
+ * Tests of the ui package.
*/
public class UISuite {
public static Test suite() {
@@ -120,6 +121,7 @@
suite.addTestSuite(HyperlinkTest.class);
suite.addTestSuite(ImageBundleGeneratorTest.class);
suite.addTestSuite(ImageTest.class);
+ suite.addTestSuite(LazyPanelTest.class);
suite.addTestSuite(LinearPanelTest.class);
suite.addTestSuite(ListBoxTest.class);
suite.addTestSuite(MenuBarTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/CompositeTest.java b/user/test/com/google/gwt/user/client/ui/CompositeTest.java
index b037981..37abcf8 100644
--- a/user/test/com/google/gwt/user/client/ui/CompositeTest.java
+++ b/user/test/com/google/gwt/user/client/ui/CompositeTest.java
@@ -44,6 +44,7 @@
boolean domFocusFired;
boolean domBlurFired;
+ @SuppressWarnings("deprecation")
public EventTestComposite() {
initWidget(tb);
sinkEvents(Event.FOCUSEVENTS);
diff --git a/user/test/com/google/gwt/user/client/ui/LazyPanelTest.java b/user/test/com/google/gwt/user/client/ui/LazyPanelTest.java
new file mode 100644
index 0000000..dfd0e3c
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/LazyPanelTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests {@link LazyPanel}.
+ */
+public class LazyPanelTest extends GWTTestCase {
+ private final static class MyLazyPanel extends LazyPanel {
+ final Widget widgetToCreate;
+ boolean createWasCalled;
+
+ public MyLazyPanel(Widget widgetToCreate) {
+ this.widgetToCreate = widgetToCreate;
+ }
+
+ @Override
+ protected Widget createWidget() {
+ createWasCalled = true;
+ return widgetToCreate;
+ }
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
+ }
+
+ public void testSetVisible() {
+ Widget w = new Label();
+ MyLazyPanel p = new MyLazyPanel(w);
+ assertNull(p.getWidget());
+ assertNull(w.getParent());
+
+ RootPanel.get().add(p);
+ assertNull(w.getParent());
+
+ p.setVisible(true);
+ assertWidgetIsChildOf(w, p);
+
+ p.createWasCalled = false;
+ p.setVisible(false);
+ assertFalse(p.createWasCalled);
+
+ p.setVisible(true);
+ assertFalse("Should not call createWidget again", p.createWasCalled);
+ }
+
+ public void testEnsureWidget() {
+ Widget w = new Label();
+ MyLazyPanel p = new MyLazyPanel(w);
+
+ p.ensureWidget();
+ assertWidgetIsChildOf(w, p);
+ assertEquals(w, p.getWidget());
+ }
+
+ private void assertWidgetIsChildOf(Widget w, Widget p) {
+ Widget parentCursor = w;
+ while (parentCursor != null && parentCursor != RootPanel.get()) {
+ parentCursor = parentCursor.getParent();
+ if (p.equals(parentCursor)) {
+ break;
+ }
+ }
+ assertEquals("Expect w to be child of p", p, parentCursor);
+ }
+
+ public void testInDeckPanel() {
+ // There are separate paths for the first widget displayed
+ // and for succeeding, so test both (see DeckPanel#showWidget)
+
+ DeckPanel deck = new DeckPanel();
+
+ Widget w0 = new Label();
+ deck.insert(new MyLazyPanel(w0), 0);
+ assertNull(w0.getParent());
+
+ Widget w1 = new Label();
+ deck.insert(new MyLazyPanel(w1), 1);
+ assertNull(w0.getParent());
+ assertNull(w1.getParent());
+
+ deck.showWidget(0);
+ assertWidgetIsChildOf(w0, deck);
+ assertNull(w1.getParent());
+
+ deck.showWidget(1);
+ assertWidgetIsChildOf(w1, deck);
+ }
+
+ public void testInStackPanel() {
+ StackPanel stack = new StackPanel();
+ stack.add(new Label(), "Able");
+
+ Widget w = new Label();
+ stack.add(new MyLazyPanel(w), "Baker");
+ assertNull(w.getParent());
+
+ stack.showStack(1);
+ assertWidgetIsChildOf(w, stack);
+ }
+
+ public void testInDisclosurePanel() {
+ Widget w = new Label();
+ DisclosurePanel dp = new DisclosurePanel();
+
+ dp.add(new MyLazyPanel(w));
+ assertNull(w.getParent());
+
+ dp.setOpen(true);
+ assertWidgetIsChildOf(w, dp);
+ }
+
+ public void testInAnimatedDisclosurePanel() {
+ Widget w = new Label();
+ DisclosurePanel dp = new DisclosurePanel();
+ dp.setAnimationEnabled(true);
+
+ dp.add(new MyLazyPanel(w));
+ assertNull(w.getParent());
+
+ dp.setOpen(true);
+ assertWidgetIsChildOf(w, dp);
+ }
+}