Update from trunk
svn merge https://google-web-toolkit.googlecode.com/svn/trunk -r6101:6142


git-svn-id: https://google-web-toolkit.googlecode.com/svn/branches/farewellSwt@6158 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/branch-info.txt b/branch-info.txt
index 3068726..3e4351b 100644
--- a/branch-info.txt
+++ b/branch-info.txt
@@ -6,3 +6,4 @@
 
 Merged from trunk
 ================
+svn merge https://google-web-toolkit.googlecode.com/svn/trunk -r6101:6142
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
index 5b47004..02f8348 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
@@ -46,7 +46,8 @@
     if (options.isUseGuiLogger()) {
       // Initialize a tree logger window.
       DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow.getInstance(
-          "Build Output for " + options.getModuleNames(), 800, 600, true);
+          "Build Output for " + options.getModuleNames(), 800, 600, true,
+          options.getLogLevel());
 
       // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
       BootStrapPlatform.initGui();
diff --git a/dev/core/src/com/google/gwt/dev/HostedModeBase.java b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
index 1496a6d..44fd711 100644
--- a/dev/core/src/com/google/gwt/dev/HostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
@@ -86,6 +86,37 @@
   }
 
   /**
+   * 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 {
@@ -232,8 +263,9 @@
     }
   }
 
-  protected interface HostedModeBaseOptions extends JJSOptions, OptionLogLevel,
-      OptionGenDir, OptionNoServer, OptionPort, OptionStartupURLs {
+  protected interface HostedModeBaseOptions extends JJSOptions, OptionLogDir,
+      OptionLogLevel, OptionGenDir, OptionNoServer, OptionPort,
+      OptionStartupURLs {
 
     /**
      * The base shell work directory.
@@ -248,6 +280,7 @@
       PrecompileOptionsImpl implements HostedModeBaseOptions {
 
     private boolean isNoServer;
+    private File logDir;
     private int port;
     private final List<String> startupURLs = new ArrayList<String>();
 
@@ -255,6 +288,20 @@
       startupURLs.add(url);
     }
 
+    public boolean alsoLogToFile() {
+      return logDir != null;
+    }
+
+    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;
     }
@@ -271,6 +318,10 @@
       return isNoServer;
     }
 
+    public void setLogFile(String filename) {
+      logDir = new File(filename);
+    }
+
     public void setNoServer(boolean isNoServer) {
       this.isNoServer = isNoServer;
     }
@@ -281,6 +332,20 @@
   }
 
   /**
+   * 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.
    * 
    */
@@ -317,6 +382,7 @@
       registerHandler(new ArgHandlerPort(options));
       registerHandler(new ArgHandlerWhitelist());
       registerHandler(new ArgHandlerBlacklist());
+      registerHandler(new ArgHandlerLogDir(options));
       registerHandler(new ArgHandlerLogLevel(options));
       registerHandler(new ArgHandlerGenDir(options));
       registerHandler(new ArgHandlerScriptStyle(options));
@@ -480,7 +546,7 @@
     // Initialize the logger.
     //
     initializeLogger();
-    
+
     // Check for updates
     final TreeLogger logger = getTopLogger();
     final CheckForUpdates updateChecker
diff --git a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
index 1633bfe..38ee586 100644
--- a/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/SwtHostedModeBase.java
@@ -285,7 +285,9 @@
     shell.setImages(ShellMainWindow.getIcons());
 
     mainWnd = new ShellMainWindow(this, shell, getTitleText(),
-        options.isNoServer() ? 0 : getPort());
+        options.isNoServer() ? 0 : getPort(), 
+        options.alsoLogToFile() ? options.getLogFile("hosted.log") : null,
+        options.getLogLevel());
 
     shell.setSize(700, 600);
     if (!isHeadless()) {
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java b/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
index 2af6125..8a3bebc 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.dev.shell.BrowserWindowController.WebServerRestart;
 import com.google.gwt.dev.shell.log.TreeLoggerWidget;
 import com.google.gwt.dev.util.Util;
@@ -38,6 +39,8 @@
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.ToolItem;
 
+import java.io.File;
+
 /**
  * Implements the GWTShell's main window control.
  */
@@ -201,7 +204,8 @@
   private Toolbar toolbar;
 
   public ShellMainWindow(BrowserWindowController browserWindowController,
-      Shell parent, String titleText, int serverPort) {
+      Shell parent, String titleText, int serverPort, File logFile,
+      Type logLevel) {
     super(parent, SWT.NONE);
     this.browserWindowController = browserWindowController;
 
@@ -235,7 +239,7 @@
 
     // Create the log pane.
     {
-      logPane = new TreeLoggerWidget(this);
+      logPane = new TreeLoggerWidget(this, logFile, logLevel);
       GridData data = new GridData();
       data.grabExcessHorizontalSpace = true;
       data.grabExcessVerticalSpace = true;
diff --git a/dev/core/src/com/google/gwt/dev/shell/log/DetachedTreeLoggerWindow.java b/dev/core/src/com/google/gwt/dev/shell/log/DetachedTreeLoggerWindow.java
index 5a44b99..0aaf638 100644
--- a/dev/core/src/com/google/gwt/dev/shell/log/DetachedTreeLoggerWindow.java
+++ b/dev/core/src/com/google/gwt/dev/shell/log/DetachedTreeLoggerWindow.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.shell.log;
 
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.dev.shell.LowLevel;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 
@@ -42,10 +43,10 @@
    */
   public static synchronized DetachedTreeLoggerWindow getInstance(
       final String caption, final int width, final int height,
-      final boolean autoScroll) {
+      final boolean autoScroll, Type logLevel) {
     if (singleton == null) {
       singleton = new DetachedTreeLoggerWindow(caption, width, height,
-          autoScroll);
+          autoScroll, logLevel);
     }
     return singleton;
   }
@@ -55,7 +56,7 @@
   private boolean isRunning = false;
 
   private DetachedTreeLoggerWindow(final String caption, final int width,
-      final int height, final boolean autoScroll) {
+      final int height, final boolean autoScroll, Type logLevel) {
 
     shell = new Shell(Display.getCurrent());
     shell.setText(caption);
@@ -64,7 +65,8 @@
     fillLayout.marginHeight = 0;
     shell.setLayout(fillLayout);
 
-    final TreeLoggerWidget treeLoggerWidget = new TreeLoggerWidget(shell);
+    final TreeLoggerWidget treeLoggerWidget = new TreeLoggerWidget(shell, null,
+        logLevel);
     treeLoggerWidget.setAutoScroll(autoScroll);
     logger = treeLoggerWidget.getLogger();
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/log/TreeLoggerWidget.java b/dev/core/src/com/google/gwt/dev/shell/log/TreeLoggerWidget.java
index 47f80d0..2bfc484 100644
--- a/dev/core/src/com/google/gwt/dev/shell/log/TreeLoggerWidget.java
+++ b/dev/core/src/com/google/gwt/dev/shell/log/TreeLoggerWidget.java
@@ -15,10 +15,14 @@
  */
 package com.google.gwt.dev.shell.log;
 
+import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.TreeLogger.HelpInfo;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.dev.shell.BrowserWidget;
 import com.google.gwt.dev.shell.log.TreeItemLogger.LogEvent;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.CompositeTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.SashForm;
@@ -43,6 +47,8 @@
 import org.eclipse.swt.widgets.Tree;
 import org.eclipse.swt.widgets.TreeItem;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.URL;
@@ -57,11 +63,19 @@
 
   private final Text details;
 
-  private final TreeItemLogger logger;
+  private final AbstractTreeLogger logger;
+  
+  private final TreeItemLogger uiLogger;
 
   private final Tree tree;
 
-  public TreeLoggerWidget(Composite parent) {
+  /**
+   * Creates a graphical widget, and optionally logging to file as well.
+   * 
+   * @param parent the graphical interface parent
+   * @param logFile the file to log to
+   */
+  public TreeLoggerWidget(Composite parent, File logFile, Type logLevel) {
     super(parent, SWT.NONE);
 
     setLayout(new FillLayout());
@@ -97,7 +111,21 @@
       }
     });
 
-    logger = new TreeItemLogger();
+    uiLogger = new TreeItemLogger();
+    AbstractTreeLogger bestLogger = uiLogger;
+    if (logFile != null) {
+      try {
+        PrintWriterTreeLogger fileLogger = new PrintWriterTreeLogger(logFile);
+        bestLogger = new CompositeTreeLogger(uiLogger, fileLogger);
+        fileLogger.setMaxDetail(logLevel);
+        uiLogger.setMaxDetail(logLevel);
+      } catch (IOException ex) {
+        uiLogger.log(TreeLogger.ERROR, "Can't log to " +
+            logFile.getAbsolutePath(), ex);
+      }
+    }
+    logger = bestLogger;
+    logger.setMaxDetail(logLevel);
 
     // The detail
     details = new Text(sash, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY | SWT.BORDER
@@ -238,7 +266,7 @@
           return;
         }
 
-        if (logger.uiFlush(tree)) {
+        if (uiLogger.uiFlush(tree)) {
           // Sync to the end of the tree.
           //
           if (autoScroll) {
diff --git a/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java b/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
index 2c7af4c..d128e8d 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/AbstractTreeLogger.java
@@ -85,15 +85,15 @@
   }
 
   public int indexWithinMyParent;
+  
+  protected TreeLogger.Type logLevel = TreeLogger.ALL;
 
-  private TreeLogger.Type logLevel = TreeLogger.ALL;
+  protected AbstractTreeLogger parent;
 
   private int nextChildIndex;
 
   private final Object nextChildIndexLock = new Object();
 
-  private AbstractTreeLogger parent;
-
   private UncommittedBranchData uncommitted;
 
   /**
@@ -226,6 +226,38 @@
     return getLoggerId();
   }
 
+  protected int allocateNextChildIndex() {
+    synchronized (nextChildIndexLock) {
+      // postincrement because we want indices to start at 0
+      return nextChildIndex++;
+    }
+  }
+
+  /**
+   * Commits the branch after ensuring that the parent logger (if there is one)
+   * has been committed first.
+   */
+  protected synchronized void commitMyBranchEntryInMyParentLogger() {
+    // (Only the root logger doesn't have a parent.)
+    //
+    if (parent != null) {
+      if (uncommitted != null) {
+        // Commit the parent first.
+        //
+        parent.commitMyBranchEntryInMyParentLogger();
+  
+        // Let the subclass do its thing to commit this branch.
+        //
+        parent.doCommitBranch(this, uncommitted.type, uncommitted.message,
+            uncommitted.caught, uncommitted.helpInfo);
+  
+        // Release the uncommitted state.
+        //
+        uncommitted = null;
+      }
+    }
+  }
+
   /**
    * Derived classes should override this method to return a branched logger.
    */
@@ -267,13 +299,6 @@
   protected abstract void doLog(int indexOfLogEntryWithinParentLogger,
       TreeLogger.Type type, String msg, Throwable caught, HelpInfo helpInfo);
 
-  private int allocateNextChildIndex() {
-    synchronized (nextChildIndexLock) {
-      // postincrement because we want indices to start at 0
-      return nextChildIndex++;
-    }
-  }
-
   /**
    * Scans <code>t</code> and its causes for {@link OutOfMemoryError}.
    * 
@@ -293,31 +318,6 @@
     return false;
   }
 
-  /**
-   * Commits the branch after ensuring that the parent logger (if there is one)
-   * has been committed first.
-   */
-  private synchronized void commitMyBranchEntryInMyParentLogger() {
-    // (Only the root logger doesn't have a parent.)
-    //
-    if (parent != null) {
-      if (uncommitted != null) {
-        // Commit the parent first.
-        //
-        parent.commitMyBranchEntryInMyParentLogger();
-
-        // Let the subclass do its thing to commit this branch.
-        //
-        parent.doCommitBranch(this, uncommitted.type, uncommitted.message,
-            uncommitted.caught, uncommitted.helpInfo);
-
-        // Release the uncommitted state.
-        //
-        uncommitted = null;
-      }
-    }
-  }
-
   private String getLoggerId() {
     if (parent != null) {
       if (parent.parent == null) {
diff --git a/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java b/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java
new file mode 100644
index 0000000..c88f461
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/log/CompositeTreeLogger.java
@@ -0,0 +1,60 @@
+/**

+ * 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.log;

+

+/**

+ * Forks logging over two child loggers.  This provides the graphics + file

+ * logging of HostedModeBase's -logfile option.

+ */

+public class CompositeTreeLogger extends AbstractTreeLogger {

+

+  private AbstractTreeLogger[] loggers;

+  

+  public CompositeTreeLogger(AbstractTreeLogger... loggers) {

+    this.loggers = loggers;

+  }

+

+  @Override

+  protected AbstractTreeLogger doBranch() {

+    AbstractTreeLogger children[] = new AbstractTreeLogger[loggers.length];

+    for (int i = 0; i < loggers.length; i++) {

+      children[i] = loggers[i].doBranch();      

+      children[i].indexWithinMyParent = loggers[i].allocateNextChildIndex();

+      children[i].parent = loggers[i];

+      children[i].logLevel = loggers[i].logLevel;

+    }

+    return new CompositeTreeLogger(children);

+  }

+

+  @Override

+  protected void doCommitBranch(AbstractTreeLogger childBeingCommitted,

+      Type type, String msg, Throwable caught, HelpInfo helpInfo) {

+    CompositeTreeLogger child = (CompositeTreeLogger) childBeingCommitted;

+    assert loggers.length == child.loggers.length;

+    for (int i = 0; i < loggers.length; i++) {

+      loggers[i].doCommitBranch(child.loggers[i], type, msg, caught, helpInfo);      

+    }

+  }

+

+  @Override

+  protected void doLog(int indexOfLogEntryWithinParentLogger, Type type,

+      String msg, Throwable caught, HelpInfo helpInfo) {

+    for (AbstractTreeLogger logger : loggers) {

+      logger.doLog(indexOfLogEntryWithinParentLogger, type, msg, caught,

+          helpInfo);

+    }

+  }

+}

diff --git a/dev/core/src/com/google/gwt/dev/util/log/PrintWriterTreeLogger.java b/dev/core/src/com/google/gwt/dev/util/log/PrintWriterTreeLogger.java
index cdd7843..6b75ab9 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/PrintWriterTreeLogger.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/PrintWriterTreeLogger.java
@@ -15,6 +15,9 @@
  */
 package com.google.gwt.dev.util.log;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.URL;
 
@@ -26,6 +29,8 @@
   private final String indent;
 
   private final PrintWriter out;
+  
+  private final Object mutex = new Object();
 
   public PrintWriterTreeLogger() {
     this(new PrintWriter(System.out, true));
@@ -34,12 +39,22 @@
   public PrintWriterTreeLogger(PrintWriter out) {
     this(out, "");
   }
+  
+  public PrintWriterTreeLogger(File logFile) throws IOException {
+    boolean existing = logFile.exists();
+    this.out = new PrintWriter(new FileWriter(logFile, true), true);
+    this.indent = "";
+    if (existing) {
+      out.println();  // blank line to mark relaunch
+    }
+  }
 
   protected PrintWriterTreeLogger(PrintWriter out, String indent) {
     this.out = out;
     this.indent = indent;
   }
 
+  @Override
   protected AbstractTreeLogger doBranch() {
     return new PrintWriterTreeLogger(out, indent + "   ");
   }
@@ -53,23 +68,25 @@
   @Override
   protected void doLog(int indexOfLogEntryWithinParentLogger, Type type,
       String msg, Throwable caught, HelpInfo helpInfo) {
-    out.print(indent);
-    if (type.needsAttention()) {
-      out.print("[");
-      out.print(type.getLabel());
-      out.print("] ");
-    }
-
-    out.println(msg);
-    if (helpInfo != null) {
-      URL url = helpInfo.getURL();
-      if (url != null) {
-        out.print(indent);
-        out.println("For additional info see: " + url.toString());
+    synchronized (mutex) { // ensure thread interleaving...
+      out.print(indent);
+      if (type.needsAttention()) {
+        out.print("[");
+        out.print(type.getLabel());
+        out.print("] ");
       }
-    }
-    if (caught != null) {
-      caught.printStackTrace(out);
+
+      out.println(msg);
+      if (helpInfo != null) {
+        URL url = helpInfo.getURL();
+        if (url != null) {
+          out.print(indent);
+          out.println("For additional info see: " + url.toString());
+        }
+      }
+      if (caught != null) {
+        caught.printStackTrace(out);
+      }
     }
   }
 }
diff --git a/dev/core/src/com/google/gwt/soyc/MakeTopLevelHtmlForPerm.java b/dev/core/src/com/google/gwt/soyc/MakeTopLevelHtmlForPerm.java
index fdf6acb..d58e825 100644
--- a/dev/core/src/com/google/gwt/soyc/MakeTopLevelHtmlForPerm.java
+++ b/dev/core/src/com/google/gwt/soyc/MakeTopLevelHtmlForPerm.java
@@ -281,24 +281,36 @@
     }
     String inputFileName = "roundedCorners.css";
     File inputFile = new File(classPath + RESOURCES_PATH + inputFileName);
+    if (!inputFile.exists()) {
+      inputFile = new File(classPath + inputFileName);
+    }
     File outputFile = getOutFile("roundedCorners.css");
     copyFileOrDirectory(inputFile, outputFile, classPath, RESOURCES_PATH
         + inputFileName, false);
 
     inputFileName = "classLevel.css";
     File inputFile2 = new File(classPath + RESOURCES_PATH + inputFileName);
+    if (!inputFile2.exists()) {
+      inputFile2 = new File(classPath + inputFileName);
+    }
     File outputFile2 = getOutFile("classLevel.css");
     copyFileOrDirectory(inputFile2, outputFile2, classPath, RESOURCES_PATH
         + inputFileName, false);
 
     inputFileName = "common.css";
     File inputFile3 = new File(classPath + RESOURCES_PATH + inputFileName);
+    if (!inputFile3.exists()) {
+      inputFile3 = new File(classPath + inputFileName);
+    }
     File outputFile3 = getOutFile("common.css");
     copyFileOrDirectory(inputFile3, outputFile3, classPath, RESOURCES_PATH
         + inputFileName, false);
 
     inputFileName = "images";
     File inputDir = new File(classPath + RESOURCES_PATH + "images");
+    if (!inputDir.exists()) {
+      inputDir = new File(classPath + "images");
+    }
     File outputDir = getOutFile("images");
     copyFileOrDirectory(inputDir, outputDir, classPath, inputFileName, true);
 
@@ -856,7 +868,12 @@
     outFile.println("<body>");
     outFile.println("<div class='abs mainHeader'>");
     outFile.println("<h2>Story of Your Compile Dashboard</h2>");
-
+    String permutationInfo = settings.allPermsInfo.get(permutationId);
+    outFile.print("<h3>Permutation " + permutationId);
+    if (permutationInfo.length() > 0) {
+      outFile.println(" (" + permutationInfo + ")");
+    }
+    outFile.println("</h3>");
     outFile.println("<hr>");
     outFile.println("<center>");
     if (globalInformation.getSplitPointToLocation().size() > 1) {
diff --git a/dev/oophm/overlay/com/google/gwt/dev/shell/ShellMainWindow.java b/dev/oophm/overlay/com/google/gwt/dev/shell/ShellMainWindow.java
index 6a94906..a58d5cf 100644
--- a/dev/oophm/overlay/com/google/gwt/dev/shell/ShellMainWindow.java
+++ b/dev/oophm/overlay/com/google/gwt/dev/shell/ShellMainWindow.java
@@ -21,6 +21,7 @@
 
 import java.awt.BorderLayout;
 import java.awt.GridLayout;
+import java.io.File;
 
 import javax.swing.BorderFactory;
 import javax.swing.JLabel;
@@ -33,7 +34,7 @@
 
   private SwingLoggerPanel logWindow;
 
-  public ShellMainWindow(TreeLogger.Type maxLevel) {
+  public ShellMainWindow(TreeLogger.Type maxLevel, File logFile) {
     super(new BorderLayout());
     // TODO(jat): add back when we have real options
     if (false) {
@@ -49,7 +50,7 @@
       panel.add(launchPanel);
       add(panel, BorderLayout.NORTH);
     }
-    logWindow = new SwingLoggerPanel(maxLevel);
+    logWindow = new SwingLoggerPanel(maxLevel, logFile);
     add(logWindow);
   }
 
diff --git a/dev/oophm/src/com/google/gwt/dev/ModulePanel.java b/dev/oophm/src/com/google/gwt/dev/ModulePanel.java
index f20ec84..281dd76 100644
--- a/dev/oophm/src/com/google/gwt/dev/ModulePanel.java
+++ b/dev/oophm/src/com/google/gwt/dev/ModulePanel.java
@@ -24,6 +24,7 @@
 import java.awt.BorderLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.File;
 
 import javax.swing.JButton;
 import javax.swing.JOptionPane;
@@ -41,7 +42,7 @@
   private boolean disconnected;
 
   public ModulePanel(Type maxLevel, String moduleName,
-      Session session) {
+      Session session, File logFile) {
     super(new BorderLayout());
     this.session = session;
     if (false) {
@@ -57,7 +58,7 @@
       topPanel.add(compileButton);
       add(topPanel, BorderLayout.NORTH);
     }
-    loggerPanel = new SwingLoggerPanel(maxLevel);
+    loggerPanel = new SwingLoggerPanel(maxLevel, logFile);
     add(loggerPanel);
     session.addModule(moduleName, this);
   }
diff --git a/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java b/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java
index e6b3107..196fa1a 100644
--- a/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java
+++ b/dev/oophm/src/com/google/gwt/dev/ModuleTabPanel.java
@@ -17,7 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.dev.OophmHostedModeBase.TabPanelCollection;
-import com.google.gwt.dev.shell.Icons;
+import com.google.gwt.dev.util.BrowserInfo;
 
 import java.awt.BorderLayout;
 import java.awt.CardLayout;
@@ -26,6 +26,7 @@
 import java.awt.Font;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.DateFormat;
@@ -226,40 +227,6 @@
   }
 
   /**
-   * Holds information about the browser used in the UI.
-   */
-  private static class BrowserInfo {
-
-    private final ImageIcon icon;
-    private final String shortName;
-
-    /**
-     * Create a BrowserInfo instance.
-     * 
-     * @param icon
-     * @param shortName
-     */
-    public BrowserInfo(ImageIcon icon, String shortName) {
-      this.icon = icon;
-      this.shortName = shortName;
-    }
-
-    /**
-     * @return the icon used to identify this browser, or null if none.
-     */
-    public ImageIcon getIcon() {
-      return icon;
-    }
-
-    /**
-     * @return the short name used to identify this browser, or null if none.
-     */
-    public String getShortName() {
-      return shortName;
-    }
-  }
-
-  /**
    * Renderer used to show entries in the module dropdown box.
    */
   private static class SessionModuleRenderer extends BasicComboBoxRenderer {
@@ -384,7 +351,7 @@
     cardLayout = new CardLayout();
     deckPanel.setLayout(cardLayout);
     add(deckPanel);
-    BrowserInfo browserInfo = getBrowserInfo(userAgent);
+    BrowserInfo browserInfo = BrowserInfo.getBrowserInfo(userAgent);
     
     // Construct the tab title and tooltip
     String tabTitle = url;
@@ -426,10 +393,11 @@
 
   public synchronized ModulePanel addModuleSession(Type maxLevel,
       String moduleName,
-      String sessionKey) {
+      String sessionKey,
+      File logFile) {
     Session session = findOrCreateSession(sessionKey);
     
-    ModulePanel panel = new ModulePanel(maxLevel, moduleName, session);
+    ModulePanel panel = new ModulePanel(maxLevel, moduleName, session, logFile);
     return panel;
   }
 
@@ -479,32 +447,6 @@
     return session;
   }
 
-  /**
-   * Choose an icon appropriate for this browser, or null if none.
-   * 
-   * @param userAgent User-Agent string from browser
-   * @return icon or null if none
-   */
-  private BrowserInfo getBrowserInfo(String userAgent) {
-    ImageIcon browserIcon = null;
-    String shortName = null;
-    String lcAgent = userAgent.toLowerCase();
-    if (lcAgent.contains("msie")) {
-      browserIcon = Icons.getIE24();
-      shortName = "IE";
-    } else if (lcAgent.contains("chrome")) {
-      browserIcon = Icons.getChrome24();
-      shortName = "Chrome";
-    } else if (lcAgent.contains("webkit") || lcAgent.contains("safari")) {
-      browserIcon = Icons.getSafari24();
-      shortName = "Safari";
-    } else if (lcAgent.contains("firefox")) {
-      browserIcon = Icons.getFirefox24();
-      shortName = "FF";
-    }
-    return new BrowserInfo(browserIcon, shortName);
-  }
-
   private String getTabTitle(URL parsedUrl) {
     String tabTitle = parsedUrl.getPath();
     if (tabTitle.length() > 0) {
diff --git a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
index bc3f169..908a578 100644
--- a/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
+++ b/dev/oophm/src/com/google/gwt/dev/OophmHostedModeBase.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.shell.OophmSessionHandler;
 import com.google.gwt.dev.shell.ShellMainWindow;
 import com.google.gwt.dev.shell.ShellModuleSpaceHost;
+import com.google.gwt.dev.util.BrowserInfo;
 import com.google.gwt.dev.util.collect.HashMap;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
@@ -35,6 +36,7 @@
 import java.awt.Cursor;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
+import java.io.File;
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -185,7 +187,10 @@
       if (!isHeadless()) {
         tabPanel = findModuleTab(userAgent, remoteSocket, url, tabKey,
             moduleName);
-        tab = tabPanel.addModuleSession(maxLevel, moduleName, sessionKey);
+        String agentTag = BrowserInfo.getShortName(userAgent).toLowerCase();
+        tab = tabPanel.addModuleSession(maxLevel, moduleName, sessionKey,
+            options.getLogFile(String.format("%s-%s-%d.log", moduleName,
+                agentTag, getNextSessionCounter(options.getLogDir()))));
         logger = tab.getLogger();
         TreeLogger branch = logger.branch(TreeLogger.INFO, "Loading module "
             + moduleName);
@@ -276,6 +281,8 @@
 
   private static final Random RNG = new Random();
   
+  private static int sessionCounter = 0;
+
   /**
    * Produce a random string that has low probability of collisions.
    * 
@@ -448,6 +455,23 @@
     return browserHost;
   }
 
+  protected int getNextSessionCounter(File logdir) {
+    if (sessionCounter == 0 && logdir != null) {
+      // first time only, figure out the "last" session count already in use
+      for (String filename : logdir.list()) {
+        if (filename.matches("^[A-Za-z0-9_$]*-[a-z]*-[0-9]*.log$")) {
+          String substring = filename.substring(filename.lastIndexOf('-') + 1,
+              filename.length() - 4);
+          int number = Integer.parseInt(substring);
+          if (number > sessionCounter) {
+            sessionCounter = number;
+          }
+        }
+      }
+    }
+    return ++sessionCounter;
+  }
+
   /**
    * @return the icon to use for the web server tab
    */
@@ -498,10 +522,15 @@
     ImageIcon gwtIcon = loadImageIcon("icon24.png");
     frame = new JFrame("GWT Development Mode");
     tabs = new JTabbedPane();
-    mainWnd = new ShellMainWindow(options.getLogLevel());
+    if (options.alsoLogToFile()) {
+      options.getLogDir().mkdirs();
+    }
+    mainWnd = new ShellMainWindow(options.getLogLevel(),
+        options.getLogFile("main.log"));
     tabs.addTab("Development Mode", gwtIcon, mainWnd, "GWT Development mode");
     if (!options.isNoServer()) {
       webServerLog = new WebServerPanel(getPort(), options.getLogLevel(),
+          options.getLogFile("webserver.log"),
           new RestartAction() {
             public void restartServer(TreeLogger logger) {
               try {
diff --git a/dev/oophm/src/com/google/gwt/dev/WebServerPanel.java b/dev/oophm/src/com/google/gwt/dev/WebServerPanel.java
index 4c45873..4fb3986 100644
--- a/dev/oophm/src/com/google/gwt/dev/WebServerPanel.java
+++ b/dev/oophm/src/com/google/gwt/dev/WebServerPanel.java
@@ -21,6 +21,7 @@
 import java.awt.BorderLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.File;
 
 import javax.swing.JButton;
 import javax.swing.JPanel;
@@ -38,14 +39,15 @@
   
   private SwingLoggerPanel logWindow;
 
-  public WebServerPanel(int serverPort, TreeLogger.Type maxLevel) {
-    this(serverPort, maxLevel, null);
+  public WebServerPanel(int serverPort, TreeLogger.Type maxLevel,
+      File logFile) {
+    this(serverPort, maxLevel, logFile, null);
   }
 
   public WebServerPanel(int serverPort, TreeLogger.Type maxLevel,
-      final RestartAction restartServerAction) {
+      File logFile, final RestartAction restartServerAction) {
     super(new BorderLayout());
-    logWindow = new SwingLoggerPanel(maxLevel);
+    logWindow = new SwingLoggerPanel(maxLevel, logFile);
     if (restartServerAction != null) {
       JPanel panel = new JPanel();
       JButton restartButton = new JButton("Restart Server");
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
index c24a316..f530a15 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingLoggerPanel.java
@@ -21,6 +21,8 @@
 import com.google.gwt.dev.shell.CloseButton.Callback;
 import com.google.gwt.dev.shell.log.SwingTreeLogger.LogEvent;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.CompositeTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -35,6 +37,8 @@
 import java.awt.event.ActionListener;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Enumeration;
 
@@ -264,7 +268,7 @@
   
   private boolean disconnected = false;
 
-  public SwingLoggerPanel(TreeLogger.Type maxLevel) {
+  public SwingLoggerPanel(TreeLogger.Type maxLevel, File logFile) {
     super(new BorderLayout());
     regexFilter = "";
     levelFilter = maxLevel;
@@ -368,8 +372,22 @@
     treeView.setMinimumSize(minSize);
     splitter.setDividerLocation(0.80);
     add(splitter);
-    logger = new SwingTreeLogger(this);
-    logger.setMaxDetail(maxLevel);
+    
+    AbstractTreeLogger uiLogger = new SwingTreeLogger(this);
+    AbstractTreeLogger bestLogger = uiLogger;
+    if (logFile != null) {
+      try {
+        PrintWriterTreeLogger fileLogger = new PrintWriterTreeLogger(logFile);
+        bestLogger = new CompositeTreeLogger(bestLogger, fileLogger);
+        fileLogger.setMaxDetail(maxLevel);
+        uiLogger.setMaxDetail(maxLevel);
+      } catch (IOException ex) {
+        bestLogger.log(TreeLogger.ERROR, "Can't log to file "
+            + logFile.getAbsolutePath(), ex);
+      }
+    }
+    bestLogger.setMaxDetail(maxLevel);
+    logger = bestLogger;
     KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F,
         InputEvent.CTRL_DOWN_MASK);
     getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(key, "find");
diff --git a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
index 520769c..c7a4a79 100644
--- a/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
+++ b/dev/oophm/src/com/google/gwt/dev/shell/log/SwingTreeLogger.java
@@ -113,7 +113,7 @@
       // Show the exception info for anything other than "UnableToComplete".
       //
       if (exceptionDetail != null) {
-        sb.append(htmlEscape(exceptionDetail));
+        sb.append("<pre>" + htmlEscape(exceptionDetail) + "</pre>");
       }
       if (helpInfo != null) {
         URL url = helpInfo.getURL();
diff --git a/dev/oophm/src/com/google/gwt/dev/util/BrowserInfo.java b/dev/oophm/src/com/google/gwt/dev/util/BrowserInfo.java
new file mode 100644
index 0000000..5f40822
--- /dev/null
+++ b/dev/oophm/src/com/google/gwt/dev/util/BrowserInfo.java
@@ -0,0 +1,100 @@
+/**

+ * Copyright 2009 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;

+

+import com.google.gwt.dev.shell.Icons;

+

+import javax.swing.ImageIcon;

+

+/**

+ * Holds information about the browser used in the UI.

+ */

+public class BrowserInfo {

+

+  private static final String UNKNOWN = "Unknown";

+  private static final String FIREFOX = "FF";

+  private static final String SAFARI = "Safari";

+  private static final String OPERA = "Opera";

+  private static final String CHROME = "Chrome";

+  private static final String IE = "IE";

+

+  /**

+   * Choose an icon and short name appropriate for this browser.  The icon

+   * may be null.

+   * 

+   * @param userAgent User-Agent string from browser

+   * @return icon or null if none

+   */

+  public static BrowserInfo getBrowserInfo(String userAgent) {

+    ImageIcon browserIcon = null;

+    String shortName = getShortName(userAgent);

+    if (shortName.equals(IE)) {

+      browserIcon = Icons.getIE24();

+    } else if (shortName.equals(CHROME)) {

+      browserIcon = Icons.getChrome24();

+    } else if (shortName.equals(OPERA)) {

+      // no icon for Opera

+    } else if (shortName.equals(SAFARI)) {

+      browserIcon = Icons.getSafari24();

+    } else if (shortName.equals(FIREFOX)) {

+      browserIcon = Icons.getFirefox24();

+    }

+    return new BrowserInfo(browserIcon, shortName);

+  }

+

+  public static String getShortName(String userAgent) {

+    String lcAgent = userAgent.toLowerCase();

+    if (lcAgent.contains("msie")) {

+      return IE;

+    } else if (lcAgent.contains("chrome")) {

+      return CHROME;

+    } else if (lcAgent.contains("opera")) {

+      return OPERA;

+    } else if (lcAgent.contains("webkit") || lcAgent.contains("safari")) {

+      return SAFARI;

+    } else if (lcAgent.contains("firefox")) {

+      return FIREFOX;

+    }

+    return UNKNOWN;

+  }

+  private final ImageIcon icon;

+  private final String shortName;

+

+  /**

+   * Create a BrowserInfo instance.

+   * 

+   * @param icon

+   * @param shortName

+   */

+  private BrowserInfo(ImageIcon icon, String shortName) {

+    this.icon = icon;

+    this.shortName = shortName;

+  }

+

+  /**

+   * @return the icon used to identify this browser, or null if none.

+   */

+  public ImageIcon getIcon() {

+    return icon;

+  }

+

+  /**

+   * @return the short name used to identify this browser, or null if none.

+   */

+  public String getShortName() {

+    return shortName;

+  }

+}
\ No newline at end of file
diff --git a/plugins/ie/oophm/oophm/oophm.vcproj b/plugins/ie/oophm/oophm/oophm.vcproj
index 6e4865c..a0cae8c 100644
--- a/plugins/ie/oophm/oophm/oophm.vcproj
+++ b/plugins/ie/oophm/oophm/oophm.vcproj
@@ -115,102 +115,6 @@
 			/>

 		</Configuration>

 		<Configuration

-			Name="Release|Win32"

-			OutputDirectory="$(ConfigurationName)32"

-			IntermediateDirectory="$(ConfigurationName)32"

-			ConfigurationType="2"

-			UseOfMFC="1"

-			UseOfATL="1"

-			ATLMinimizesCRunTimeLibraryUsage="false"

-			CharacterSet="1"

-			>

-			<Tool

-				Name="VCPreBuildEventTool"

-			/>

-			<Tool

-				Name="VCCustomBuildTool"

-			/>

-			<Tool

-				Name="VCXMLDataGeneratorTool"

-			/>

-			<Tool

-				Name="VCWebServiceProxyGeneratorTool"

-			/>

-			<Tool

-				Name="VCMIDLTool"

-				PreprocessorDefinitions="NDEBUG"

-				MkTypLibCompatible="false"

-				TargetEnvironment="1"

-				GenerateStublessProxies="true"

-				TypeLibraryName="$(IntDir)/oophm.tlb"

-				HeaderFileName="oophm_i.h"

-				DLLDataFileName=""

-				InterfaceIdentifierFileName="oophm_i.c"

-				ProxyFileName="oophm_p.c"

-				ValidateParameters="true"

-			/>

-			<Tool

-				Name="VCCLCompilerTool"

-				Optimization="2"

-				AdditionalIncludeDirectories="../../../common"

-				PreprocessorDefinitions="_WINDOWS;GWT_DEBUGDISABLE"

-				RuntimeLibrary="0"

-				UsePrecompiledHeader="0"

-				WarningLevel="3"

-				DebugInformationFormat="3"

-			/>

-			<Tool

-				Name="VCManagedResourceCompilerTool"

-			/>

-			<Tool

-				Name="VCResourceCompilerTool"

-				PreprocessorDefinitions="NDEBUG"

-				Culture="1033"

-				AdditionalIncludeDirectories="$(IntDir)"

-			/>

-			<Tool

-				Name="VCPreLinkEventTool"

-			/>

-			<Tool

-				Name="VCLinkerTool"

-				RegisterOutput="true"

-				IgnoreImportLibrary="true"

-				AdditionalDependencies="comsuppw.lib ws2_32.lib"

-				OutputFile="..\..\prebuilt\$(ProjectName).dll"

-				LinkIncremental="1"

-				ModuleDefinitionFile=".\oophm.def"

-				GenerateDebugInformation="true"

-				SubSystem="2"

-				OptimizeReferences="2"

-				EnableCOMDATFolding="2"

-				TargetMachine="1"

-			/>

-			<Tool

-				Name="VCALinkTool"

-			/>

-			<Tool

-				Name="VCManifestTool"

-			/>

-			<Tool

-				Name="VCXDCMakeTool"

-			/>

-			<Tool

-				Name="VCBscMakeTool"

-			/>

-			<Tool

-				Name="VCFxCopTool"

-			/>

-			<Tool

-				Name="VCAppVerifierTool"

-			/>

-			<Tool

-				Name="VCWebDeploymentTool"

-			/>

-			<Tool

-				Name="VCPostBuildEventTool"

-			/>

-		</Configuration>

-		<Configuration

 			Name="Debug|x64"

 			OutputDirectory="$(PlatformName)\$(ConfigurationName)"

 			IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"

@@ -305,6 +209,102 @@
 			/>

 		</Configuration>

 		<Configuration

+			Name="Release|Win32"

+			OutputDirectory="$(ConfigurationName)32"

+			IntermediateDirectory="$(ConfigurationName)32"

+			ConfigurationType="2"

+			UseOfMFC="1"

+			UseOfATL="1"

+			ATLMinimizesCRunTimeLibraryUsage="false"

+			CharacterSet="1"

+			>

+			<Tool

+				Name="VCPreBuildEventTool"

+			/>

+			<Tool

+				Name="VCCustomBuildTool"

+			/>

+			<Tool

+				Name="VCXMLDataGeneratorTool"

+			/>

+			<Tool

+				Name="VCWebServiceProxyGeneratorTool"

+			/>

+			<Tool

+				Name="VCMIDLTool"

+				PreprocessorDefinitions="NDEBUG"

+				MkTypLibCompatible="false"

+				TargetEnvironment="1"

+				GenerateStublessProxies="true"

+				TypeLibraryName="$(IntDir)/oophm.tlb"

+				HeaderFileName="oophm_i.h"

+				DLLDataFileName=""

+				InterfaceIdentifierFileName="oophm_i.c"

+				ProxyFileName="oophm_p.c"

+				ValidateParameters="true"

+			/>

+			<Tool

+				Name="VCCLCompilerTool"

+				Optimization="2"

+				AdditionalIncludeDirectories="&quot;$(ProjectDir)&quot;;../../../platform/Win;../../../common"

+				PreprocessorDefinitions="_WINDOWS;GWT_DEBUGDISABLE"

+				RuntimeLibrary="0"

+				UsePrecompiledHeader="0"

+				WarningLevel="3"

+				DebugInformationFormat="3"

+			/>

+			<Tool

+				Name="VCManagedResourceCompilerTool"

+			/>

+			<Tool

+				Name="VCResourceCompilerTool"

+				PreprocessorDefinitions="NDEBUG"

+				Culture="1033"

+				AdditionalIncludeDirectories="$(IntDir)"

+			/>

+			<Tool

+				Name="VCPreLinkEventTool"

+			/>

+			<Tool

+				Name="VCLinkerTool"

+				RegisterOutput="true"

+				IgnoreImportLibrary="true"

+				AdditionalDependencies="comsuppw.lib ws2_32.lib"

+				OutputFile="..\..\prebuilt\$(ProjectName).dll"

+				LinkIncremental="1"

+				ModuleDefinitionFile=".\oophm.def"

+				GenerateDebugInformation="true"

+				SubSystem="2"

+				OptimizeReferences="2"

+				EnableCOMDATFolding="2"

+				TargetMachine="1"

+			/>

+			<Tool

+				Name="VCALinkTool"

+			/>

+			<Tool

+				Name="VCManifestTool"

+			/>

+			<Tool

+				Name="VCXDCMakeTool"

+			/>

+			<Tool

+				Name="VCBscMakeTool"

+			/>

+			<Tool

+				Name="VCFxCopTool"

+			/>

+			<Tool

+				Name="VCAppVerifierTool"

+			/>

+			<Tool

+				Name="VCWebDeploymentTool"

+			/>

+			<Tool

+				Name="VCPostBuildEventTool"

+			/>

+		</Configuration>

+		<Configuration

 			Name="Release|x64"

 			OutputDirectory="$(ConfigurationName)64"

 			IntermediateDirectory="$(ConfigurationName)64"

@@ -342,7 +342,7 @@
 				Name="VCCLCompilerTool"

 				Optimization="2"

 				FavorSizeOrSpeed="1"

-				AdditionalIncludeDirectories="../../../common"

+				AdditionalIncludeDirectories="../../../platform/Win;../../../common"

 				PreprocessorDefinitions="_WINDOWS;GWT_DEBUGDISABLE"

 				RuntimeLibrary="0"

 				UsePrecompiledHeader="0"

@@ -410,7 +410,7 @@
 			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"

 			>

 			<File

-				RelativePath=".\AllowDialog.cpp"

+				RelativePath="..\..\..\platform\Win\AllowDialog.cpp"

 				>

 			</File>

 			<File

@@ -426,7 +426,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Release|Win32"

+					Name="Debug|x64"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -435,7 +435,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Debug|x64"

+					Name="Release|Win32"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -466,7 +466,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Release|Win32"

+					Name="Debug|x64"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -475,7 +475,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Debug|x64"

+					Name="Release|Win32"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -522,7 +522,7 @@
 				>

 			</File>

 			<File

-				RelativePath=".\Preferences.cpp"

+				RelativePath="..\..\..\platform\Win\Preferences.cpp"

 				>

 			</File>

 			<File

@@ -537,7 +537,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Release|Win32"

+					Name="Debug|x64"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -545,7 +545,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Debug|x64"

+					Name="Release|Win32"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -636,7 +636,7 @@
 			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"

 			>

 			<File

-				RelativePath=".\AllowDialog.h"

+				RelativePath="..\..\..\platform\Win\AllowDialog.h"

 				>

 			</File>

 			<File

@@ -668,7 +668,7 @@
 				>

 			</File>

 			<File

-				RelativePath=".\Preferences.h"

+				RelativePath="..\..\..\platform\Win\Preferences.h"

 				>

 			</File>

 			<File

@@ -831,7 +831,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Release|Win32"

+					Name="Debug|x64"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

@@ -840,7 +840,7 @@
 					/>

 				</FileConfiguration>

 				<FileConfiguration

-					Name="Debug|x64"

+					Name="Release|Win32"

 					>

 					<Tool

 						Name="VCCLCompilerTool"

diff --git a/plugins/npapi/LocalObjectTable.cpp b/plugins/npapi/LocalObjectTable.cpp
index e0c381c..a450381 100644
--- a/plugins/npapi/LocalObjectTable.cpp
+++ b/plugins/npapi/LocalObjectTable.cpp
@@ -1,37 +1,8 @@
 #include "mozincludes.h"
 #include "LocalObjectTable.h"
 
-// Mirrors of NPObjectWrapper and NPObjectProxy from Chrome
-// TODO(jat): this is very fragile and may break if Chrome changes
-struct ChromeNPObjectProxy {
-  // IPC::Channel::Listener and IPC::Message::Sender are pure interfaces, so we don't need to
-  // account for any data fields from their inheritance
-  void* channel_; // scoped_refptr keeps only a single pointer
-  void* unknown; // looks like another pointer before route id
-  int route_id_;
-  intptr_t npobject_ptr_;
-};
-
-struct ChromeNPObjectWrapper {
-  NPObject object;
-  ChromeNPObjectProxy* proxy;
-};
-
-NPClass* LocalObjectTable::wrappedObjectClass = 0;
-
 LocalObjectTable::~LocalObjectTable() {
   if (!dontFree) {
     freeAll();
   }
 }
-
-void* LocalObjectTable::getIdentityFrom(NPObject* obj) {
-  void* id = obj;
-  if (obj->_class == wrappedObjectClass) {
-    ChromeNPObjectWrapper* wrapper = reinterpret_cast<ChromeNPObjectWrapper*>(obj);
-    ChromeNPObjectProxy* proxy = wrapper->proxy;
-    id = reinterpret_cast<void*>(proxy->npobject_ptr_);
-    Debug::log(Debug::Info) << "Mapped obj=" << (void*)obj << " to " << id << Debug::flush;
-  }
-  return id;
-}
diff --git a/plugins/npapi/LocalObjectTable.h b/plugins/npapi/LocalObjectTable.h
index 4943ff3..9ff809d 100644
--- a/plugins/npapi/LocalObjectTable.h
+++ b/plugins/npapi/LocalObjectTable.h
@@ -18,7 +18,6 @@
 
 #include <vector>
 #include <algorithm>
-#include <map>
 
 #include "Debug.h"
 
@@ -30,9 +29,7 @@
 
   int nextFree;
   std::vector<NPObject*> objects;
-  std::map<void*, int> objectIndex;
   bool dontFree;
-  static NPClass* wrappedObjectClass;
 
   bool isFree(int id) {
     // low bit is set for free pointers, object pointers can't be odd
@@ -45,15 +42,6 @@
     nextFree = id;
   }
 
-  int findIndex(void* ptr) {
-    std::map<void*, int>::iterator it = objectIndex.find(ptr);
-    if (it == objectIndex.end()) {
-      return -1;
-    }
-    return it->second;
-  }
-
-  static void* getIdentityFrom(NPObject* obj);
 public:
   LocalObjectTable() {
     nextFree = -1;
@@ -63,18 +51,11 @@
 
   virtual ~LocalObjectTable();
 
-  static void setWrappedObjectClass(NPClass* clazz) {
-    wrappedObjectClass = clazz;
-  }
-
+  /**
+   * Add a new object, which must not be in the table, and return a new id for it.
+   */
   int add(NPObject* obj) {
-    void* objId = getIdentityFrom(obj);
-    int id = findIndex(objId);
-    if (id >= 0) {
-      Debug::log(Debug::Spam) << "LocalObjectTable::add(obj=" << obj
-          << "): returning old id=" << id << Debug::flush;
-      return id;
-    }
+    int id;
     if (nextFree >= 0) {
       id = nextFree;
       nextFree = int(reinterpret_cast<long long>(objects[nextFree])) >> 1;
@@ -83,7 +64,6 @@
       id = static_cast<int>(objects.size());
       objects.push_back(obj);
     }
-    objectIndex[objId] = id;
     Debug::log(Debug::Spam) << "LocalObjectTable::add(obj=" << obj << "): id=" << id
         << Debug::flush;
     // keep track that we hold a reference in the table
@@ -91,11 +71,6 @@
     return id;
   }
 
-  int find(NPObject* obj) {
-    void* objId = getIdentityFrom(obj);
-    return findIndex(objId);
-  }
-
   void free(int id) {
     Debug::log(Debug::Spam) << "LocalObjectTable::free(id=" << id << ")" << Debug::flush;
     if (unsigned(id) >= objects.size()) {
diff --git a/plugins/npapi/NPVariantWrapper.h b/plugins/npapi/NPVariantWrapper.h
index 0a469c9..91bcaaf 100644
--- a/plugins/npapi/NPVariantWrapper.h
+++ b/plugins/npapi/NPVariantWrapper.h
@@ -490,6 +490,12 @@
     return *this;
   }
 
+  // Convenience method for C++ code
+  NPVariantWrapper& operator=(int intval) {
+    NPVariantProxy::assignFrom(variant, intval);
+    return *this;
+  }
+
   void release() {
     NPVariantProxy::release(variant);
   }
diff --git a/plugins/npapi/ScriptableInstance.cpp b/plugins/npapi/ScriptableInstance.cpp
index b3c5c18..6a137bd 100644
--- a/plugins/npapi/ScriptableInstance.cpp
+++ b/plugins/npapi/ScriptableInstance.cpp
@@ -21,6 +21,9 @@
 #include "ReturnMessage.h"
 #include "ServerMethods.h"
 #include "AllowedConnections.h"
+#include "Preferences.h"
+#include "AllowDialog.h"
+
 #include "mozincludes.h"
 #include "scoped_ptr/scoped_ptr.h"
 #include "NPVariantWrapper.h"
@@ -32,12 +35,6 @@
   return string(GetNPStringUTF8Characters(str), GetNPStringUTF8Length(str));
 }
 
-static bool askUserToAllow(const std::string& url, bool* remember) {
-  // TODO(jat): implement, for now allow anything but don't remember
-  *remember = false;
-  return true;
-}
-
 string ScriptableInstance::computeTabIdentity() {
   return "";
 }
@@ -65,7 +62,7 @@
     toStringID(NPN_GetStringIdentifier("toString")),
     connectedID(NPN_GetStringIdentifier("connected")),
     statsID(NPN_GetStringIdentifier("stats")),
-    savedID(NPN_GetStringIdentifier("saved")),
+    gwtId(NPN_GetStringIdentifier("__gwt_ObjectId")),
     jsInvokeID(NPN_GetStringIdentifier("__gwt_jsInvoke")),
     jsResultID(NPN_GetStringIdentifier("__gwt_makeResult")),
     jsTearOffID(NPN_GetStringIdentifier("__gwt_makeTearOff")),
@@ -145,7 +142,7 @@
     return true;
   }
   // TODO: special-case toString tear-offs
-  return name == statsID || name == connectedID || name == savedID;
+  return name == statsID || name == connectedID;
 }
 
 bool ScriptableInstance::getProperty(NPIdentifier name, NPVariant* variant) {
@@ -159,13 +156,6 @@
   } else if (name == statsID) {
     NPVariantProxy::assignFrom(*variant, "<stats data>");
     retVal = true;
-  } else if (name == savedID) {
-    if (savedValueIdx >= 0) {
-      NPObject* npObj = localObjects.get(savedValueIdx);
-      OBJECT_TO_NPVARIANT(npObj, *variant);
-      NPN_RetainObject(npObj);
-    }
-    retVal = true;
   }
   if (retVal) {
     // TODO: testing
@@ -179,14 +169,6 @@
   Debug::log(Debug::Debugging) << "ScriptableInstance::setProperty(name="
       << NPN_UTF8FromIdentifier(name) << ", value=" << *variant << ")"
       << Debug::flush; 
-  if (NPN_IdentifierIsString(name)) {
-    if (name == savedID && NPVariantProxy::isObject(*variant)) {
-      Debug::log(Debug::Debugging) << " set saved value" << Debug::flush;
-      savedValueIdx = localObjects.add(NPVariantProxy::getAsObject(*variant));
-      return true;
-    }
-    return false;
-  }
   return false;
 }
 
@@ -259,7 +241,6 @@
   // replace our window object with that passed by the caller
   window = NPVariantProxy::getAsObject(args[0]);
   NPN_RetainObject(window);
-  LocalObjectTable::setWrappedObjectClass(window->_class);
   BOOLEAN_TO_NPVARIANT(true, *result);
   result->type = NPVariantType_Bool;
 }
@@ -289,16 +270,17 @@
       << ",module=" << NPVariantProxy::toString(args[3]) << ",hostedHtmlVers=" << NPVariantProxy::toString(args[4])
       << ")" << Debug::flush;
   const std::string urlStr = convertToString(url);
-  bool allowed = false;
-  // TODO(jat): load access list
-  if (!AllowedConnections::matchesRule(urlStr, &allowed)) {
-    // If we didn't match an existing rule, prompt the user
-    bool remember = false;
-    allowed = askUserToAllow(urlStr, &remember);
-    if (remember) {
-      // TODO(jat): update access list
-    }
-  }
+
+  Preferences::loadAccessList();

+  bool allowed = false;

+  if (!AllowedConnections::matchesRule(urlStr, &allowed)) {

+    bool remember = false;

+    allowed = AllowDialog::askUserToAllow(&remember);

+    if (remember) {

+      std::string host = AllowedConnections::getHostFromUrl(urlStr);

+      Preferences::addNewRule(host, !allowed);

+    }

+  }

   if (!allowed) {
     BOOLEAN_TO_NPVARIANT(false, *result);
     result->type = NPVariantType_Bool;
@@ -345,6 +327,22 @@
   result->type = NPVariantType_Bool;
 }
 
+int ScriptableInstance::getLocalObjectRef(NPObject* obj) {
+  NPVariantWrapper wrappedRetVal(*this);
+  int id;
+  if (NPN_GetProperty(getNPP(), obj, gwtId, wrappedRetVal.addressForReturn())
+      && wrappedRetVal.isInt()) {
+    id = wrappedRetVal.getAsInt();
+  } else {
+    id = localObjects.add(obj);
+    wrappedRetVal = id;
+    if (!NPN_SetProperty(getNPP(), obj, gwtId, wrappedRetVal.address())) {
+      Debug::log(Debug::Error) << "Setting GWT id on object failed" << Debug::flush;
+    }
+  }
+  return id;
+}
+
 void ScriptableInstance::fatalError(HostChannel& channel, const std::string& message) {
   // TODO(jat): better error handling
   Debug::log(Debug::Error) << "Fatal error: " << message << Debug::flush;
@@ -362,7 +360,12 @@
   Debug::log(Debug::Debugging) << "freeValue(#ids=" << idCount << ")" << Debug::flush;
   for (int i = 0; i < idCount; ++i) {
 	  Debug::log(Debug::Spam) << " id=" << ids[i] << Debug::flush;
-    localObjects.free(ids[i]);
+    NPObject* obj = localObjects.get(ids[i]);
+    if (!NPN_RemoveProperty(getNPP(), obj, gwtId)) {
+      Debug::log(Debug::Error) << "Unable to remove GWT ID from object " << ids[i] << Debug::flush;
+    } else {
+      localObjects.free(ids[i]);
+    }
   }
 }
 
diff --git a/plugins/npapi/ScriptableInstance.h b/plugins/npapi/ScriptableInstance.h
index 035f823..cdce6f3 100644
--- a/plugins/npapi/ScriptableInstance.h
+++ b/plugins/npapi/ScriptableInstance.h
@@ -63,7 +63,7 @@
   
   void dumpJSresult(const char* js);
   
-  int getLocalObjectRef(NPObject* obj) { return localObjects.add(obj); }
+  int getLocalObjectRef(NPObject* obj);
   NPObject* getLocalObject(int refid) { return localObjects.get(refid); }
   
   bool tryGetStringPrimitive(NPObject* obj, NPVariant& result);
@@ -92,7 +92,7 @@
   
   const NPIdentifier connectedID;
   const NPIdentifier statsID;
-  const NPIdentifier savedID;
+  const NPIdentifier gwtId;
 
   const NPIdentifier jsInvokeID;
   const NPIdentifier jsResultID;
diff --git a/plugins/npapi/VisualStudio/npapi-plugin.vcproj b/plugins/npapi/VisualStudio/npapi-plugin.vcproj
index a0e4545..d338c7f 100755
--- a/plugins/npapi/VisualStudio/npapi-plugin.vcproj
+++ b/plugins/npapi/VisualStudio/npapi-plugin.vcproj
@@ -39,7 +39,7 @@
 			<Tool

 				Name="VCCLCompilerTool"

 				Optimization="0"

-				AdditionalIncludeDirectories="&quot;$(ProjectDir)\..\..\common&quot;"

+				AdditionalIncludeDirectories="&quot;$(ProjectDir)\..\..\platform\Win&quot;;&quot;$(ProjectDir)\..&quot;;&quot;$(ProjectDir)\..\..\common&quot;"

 				PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;FIREFOXPLUGIN_EXPORTS"

 				MinimalRebuild="true"

 				BasicRuntimeChecks="3"

@@ -63,7 +63,7 @@
 				Name="VCLinkerTool"

 				AdditionalDependencies="ws2_32.lib"

 				ShowProgress="2"

-				OutputFile="$(ProjectDir)\..\prebuilt\WINNT_x86-msvc\npOOPHM.dll"

+				OutputFile="$(ProjectDir)\..\prebuilt\gwtdmp\WINNT_x86-msvc\npOOPHM.dll"

 				LinkIncremental="1"

 				ModuleDefinitionFile="$(ProjectDir)\..\npOOPHM.def"

 				GenerateDebugInformation="true"

@@ -122,7 +122,7 @@
 				Name="VCCLCompilerTool"

 				Optimization="3"

 				EnableIntrinsicFunctions="true"

-				AdditionalIncludeDirectories="&quot;..\..\common&quot;"

+				AdditionalIncludeDirectories="&quot;$(ProjectDir)\..\..\platform\Win&quot;;&quot;$(ProjectDir)\..&quot;;&quot;$(ProjectDir)\..\..\common&quot;"

 				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;FIREFOXPLUGIN_EXPORTS"

 				ExceptionHandling="1"

 				RuntimeLibrary="2"

@@ -144,7 +144,7 @@
 				Name="VCLinkerTool"

 				AdditionalDependencies="ws2_32.lib"

 				ShowProgress="2"

-				OutputFile="..\extension\platform\WINNT_x86-msvc\plugins\npOOPHM.dll"

+				OutputFile="$(ProjectDir)\..\prebuilt\gwtdmp\WINNT_x86-msvc\npOOPHM.dll"

 				LinkIncremental="0"

 				AdditionalLibraryDirectories=""

 				ModuleDefinitionFile="..\npOOPHM.def"

@@ -191,6 +191,10 @@
 			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"

 			>

 			<File

+				RelativePath="..\..\platform\Win\AllowDialog.h"

+				>

+			</File>

+			<File

 				RelativePath="..\..\common\AllowedConnections.h"

 				>

 			</File>

@@ -291,6 +295,10 @@
 				>

 			</File>

 			<File

+				RelativePath="..\..\platform\Win\Preferences.h"

+				>

+			</File>

+			<File

 				RelativePath="..\..\common\ProtocolVersionMessage.h"

 				>

 			</File>

@@ -351,6 +359,10 @@
 			UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"

 			>

 			<File

+				RelativePath="..\..\platform\Win\AllowDialog.cpp"

+				>

+			</File>

+			<File

 				RelativePath="..\..\common\AllowedConnections.cpp"

 				>

 			</File>

@@ -419,6 +431,10 @@
 				>

 			</File>

 			<File

+				RelativePath="..\..\platform\Win\Preferences.cpp"

+				>

+			</File>

+			<File

 				RelativePath="..\..\common\ProtocolVersionMessage.cpp"

 				>

 			</File>

diff --git a/plugins/npapi/extension/platform/WINNT_x86-msvc/plugins/npOOPHM.dll b/plugins/npapi/extension/platform/WINNT_x86-msvc/plugins/npOOPHM.dll
deleted file mode 100755
index dfcdb9b..0000000
--- a/plugins/npapi/extension/platform/WINNT_x86-msvc/plugins/npOOPHM.dll
+++ /dev/null
Binary files differ
diff --git a/plugins/npapi/main.cpp b/plugins/npapi/main.cpp
index 065c0c0..27338ac 100644
--- a/plugins/npapi/main.cpp
+++ b/plugins/npapi/main.cpp
@@ -17,11 +17,13 @@
 #include "Plugin.h"
 #include "ScriptableInstance.h"
 #include "scoped_ptr/scoped_ptr.h"
+#include "AllowDialog.h"
 
 #ifdef _WINDOWS
 #include <windows.h>
 
 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, LPVOID lpReserved) {
+  AllowDialog::setHInstance(hModule);

   switch (ulReasonForCall) {
     case DLL_PROCESS_ATTACH:
     case DLL_THREAD_ATTACH:
@@ -280,11 +282,11 @@
     Debug::log(Debug::Info) << "NP_GetValue(var=" << variable << ")" << Debug::flush;
     switch (variable) {
       case NPPVpluginNameString:
-        *static_cast<const char **>(value) = "GWT Hosted-mode Plugin";
+        *static_cast<const char **>(value) = "GWT Development-Mode Plugin";
         break;
       case NPPVpluginDescriptionString:
         *static_cast<const char **>(value) = "Plugin to enable debugging of Google Web Toolkit "
-            "applications in hosted mode.";
+            "applications in development mode.";
         break;
       default:
         Debug::log(Debug::Info) << "NPP_GetValue(var=" << variable
diff --git a/plugins/npapi/npOOPHM.rc b/plugins/npapi/npOOPHM.rc
index 8c0f920..ed2790b 100644
--- a/plugins/npapi/npOOPHM.rc
+++ b/plugins/npapi/npOOPHM.rc
@@ -1,73 +1,111 @@
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-#include "windows.h"
-#undef APSTUDIO_READONLY_SYMBOLS
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-#ifdef _WIN32
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-#pragma code_page(1252)
-#endif
-
-1 VERSIONINFO
-  FILEVERSION 0,1,1,0
-  PRODUCTVERSION 0,1,1,0
-  FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
-  FILEFLAGS 0x1L
-#else
-  FILEFLAGS 0x0L
-#endif
-  FILEOS 0x40004L
-  FILETYPE 0x2L
-  FILESUBTYPE 0x0L
-BEGIN
-  BLOCK "StringFileInfo"
-  BEGIN
-  	BLOCK "040904e4"
-  	BEGIN
-  	  VALUE "CompanyName", 		"Google Inc"
-  	  VALUE "FileDescription",	"GWT OOPHM Plugin"
-#if 0
-  	  VALUE "FileExtents",		""
-#endif
-  	  VALUE "FileOpenName",		"Plugin to allow debugging of GWT applications in hosted mode."
-  	  VALUE "FileVersion",		"0.1a"
-  	  VALUE "InternalName",		"GWT OOPHM Plugin"
-  	  VALUE "LegalCopyright",	"Copyright © 2008 Google Inc.  All rights reserved."
-  	  VALUE "MIMEType",			"application/x-gwt-hosted-mode"
-  	  VALUE "OriginalFilename",	"npOOPHM.dll"
-  	  VALUE "ProductName",		"GWT OOPHM Plugin"
-  	  VALUE "ProductVersion",	"0.1a"
-  	END
-  END
-  BLOCK "VarFileInfo"
-  BEGIN
-    VALUE "Translation", 0x409, 1252
-  END
-END
-
-#ifdef APSTUDIO_INVOKED
-1 TEXTINCLUDE
-BEGIN
-  "resource.h\0"
-END
-
-2 TEXTINCLUDE
-BEGIN
-  "#include ""mfc/afxres.h""\r\n"
-  "\0"
-END
-
-3 TEXTINCLUDE
-BEGIN
-  "\r\n"
-  "\0"
-END
-
-#endif
-
-#else
-
-#endif
+#include "resource.h"

+

+#define APSTUDIO_READONLY_SYMBOLS

+#include "windows.h"

+#include "winres.h"

+#undef APSTUDIO_READONLY_SYMBOLS

+

+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)

+#ifdef _WIN32

+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

+#pragma code_page(1252)

+#endif

+

+1 VERSIONINFO

+  FILEVERSION 0,9,0,0

+  PRODUCTVERSION 0,9,0,0

+  FILEFLAGSMASK 0x3fL

+#ifdef _DEBUG

+  FILEFLAGS 0x1L

+#else

+  FILEFLAGS 0x0L

+#endif

+  FILEOS 0x40004L

+  FILETYPE 0x2L

+  FILESUBTYPE 0x0L

+BEGIN

+  BLOCK "StringFileInfo"

+  BEGIN

+  	BLOCK "040904e4"

+  	BEGIN

+  	  VALUE "CompanyName", 		"Google Inc"

+  	  VALUE "FileDescription",	"GWT Development Mode Plugin"

+#if 0

+  	  VALUE "FileExtents",		""

+#endif

+  	  VALUE "FileOpenName",		"Plugin to allow debugging of GWT applications in hosted mode."

+  	  VALUE "FileVersion",		"0.9.0"

+  	  VALUE "InternalName",		"GWT DMP"

+  	  VALUE "LegalCopyright",	"Copyright © 2009 Google Inc.  All rights reserved."

+  	  VALUE "MIMEType",			"application/x-gwt-hosted-mode"

+  	  VALUE "OriginalFilename",	"npOOPHM.dll"

+  	  VALUE "ProductName",		"GWT DMP Plugin"

+  	  VALUE "ProductVersion",	"0.9.0"

+  	END

+  END

+  BLOCK "VarFileInfo"

+  BEGIN

+    VALUE "Translation", 0x409, 1252

+  END

+END

+

+#ifdef APSTUDIO_INVOKED

+1 TEXTINCLUDE

+BEGIN

+  "resource.h\0"

+END

+

+2 TEXTINCLUDE

+BEGIN

+  "#include ""mfc/afxres.h""\r\n"

+  "\0"

+END

+

+3 TEXTINCLUDE

+BEGIN

+  "\r\n"

+  "\0"

+END

+

+#endif

+

+/////////////////////////////////////////////////////////////////////////////

+//

+// Dialog

+//

+

+IDD_ALLOW_DIALOG DIALOGEX 0, 0, 188, 73

+STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_CAPTION

+EXSTYLE WS_EX_TOPMOST

+CAPTION "GWT Plugin Security Alert"

+FONT 10, "Microsoft Sans Serif", 400, 0, 0x0

+BEGIN

+    CONTROL         "Remember this decision for this server",IDC_REMEMBER_CHECKBOX,

+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,30,31,129,10

+    LTEXT           "This web server is trying to initiate a GWT Development\r\nMode Conncetion -- should it be allowed?",IDC_STATIC,10,7,167,19

+    PUSHBUTTON      "Allow",IDC_ALLOW_BUTTON,37,50,50,14

+    DEFPUSHBUTTON   "Deny",IDCANCEL,100,50,50,14

+END

+

+

+/////////////////////////////////////////////////////////////////////////////

+//

+// DESIGNINFO

+//

+

+#ifdef APSTUDIO_INVOKED

+GUIDELINES DESIGNINFO 

+BEGIN

+    IDD_ALLOW_DIALOG, DIALOG

+    BEGIN

+        LEFTMARGIN, 7

+        RIGHTMARGIN, 181

+        TOPMARGIN, 7

+        BOTTOMMARGIN, 66

+    END

+END

+#endif    // APSTUDIO_INVOKED

+

+#else

+

+#endif

diff --git a/plugins/npapi/prebuilt/extension/chrome.manifest b/plugins/npapi/prebuilt/extension/chrome.manifest
deleted file mode 100644
index 38a0fdd..0000000
--- a/plugins/npapi/prebuilt/extension/chrome.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-content gwt-oophm content/
-skin gwt-oophm classic/1.0 skin/
diff --git a/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/Info.plist b/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/Info.plist
deleted file mode 100644
index 234ad6e..0000000
--- a/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/Info.plist
+++ /dev/null
@@ -1,20 +0,0 @@
-<plist version="1.0">
-<dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>English</string>
-	<key>CFBundleExecutable</key>
-	<string>liboophm</string>
-	<key>CFBundleIdentifier</key>
-	<string>com.google.gwt.oophm</string>
-	<key>CFBundleName</key>
-	<string>oophm</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundlePackageType</key>
-	<string>NSPL</string>
-	<key>CFBundleSignature</key>
-	<string>MOSS</string>
-	<key>CFBundleVersion</key>
-	<string>1.0</string>
-</dict>
-</plist>
diff --git a/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/MacOS/liboophm b/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/MacOS/liboophm
deleted file mode 100755
index 49d5a1f..0000000
--- a/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/MacOS/liboophm
+++ /dev/null
Binary files differ
diff --git a/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/Resources/liboophm.rsrc b/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/Resources/liboophm.rsrc
deleted file mode 100644
index c1cb961..0000000
--- a/plugins/npapi/prebuilt/extension/platform/Darwin_x86-gcc3/plugins/oophm.plugin/Contents/Resources/liboophm.rsrc
+++ /dev/null
Binary files differ
diff --git a/plugins/npapi/prebuilt/extension/platform/WINNT_x86-msvc/plugins/npOOPHM.dll b/plugins/npapi/prebuilt/extension/platform/WINNT_x86-msvc/plugins/npOOPHM.dll
deleted file mode 100755
index dfcdb9b..0000000
--- a/plugins/npapi/prebuilt/extension/platform/WINNT_x86-msvc/plugins/npOOPHM.dll
+++ /dev/null
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp.crx b/plugins/npapi/prebuilt/gwtdmp.crx
new file mode 100644
index 0000000..1538642
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp.crx
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp/WINNT_x86-msvc/npOOPHM.dll b/plugins/npapi/prebuilt/gwtdmp/WINNT_x86-msvc/npOOPHM.dll
new file mode 100644
index 0000000..a1dcaa9
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp/WINNT_x86-msvc/npOOPHM.dll
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp/gwt128.png b/plugins/npapi/prebuilt/gwtdmp/gwt128.png
new file mode 100644
index 0000000..57e8e01
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp/gwt128.png
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp/gwt16.png b/plugins/npapi/prebuilt/gwtdmp/gwt16.png
new file mode 100644
index 0000000..f26096e
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp/gwt16.png
Binary files differ
diff --git a/plugins/npapi/prebuilt/extension/skin/icon.png b/plugins/npapi/prebuilt/gwtdmp/gwt32.png
similarity index 100%
rename from plugins/npapi/prebuilt/extension/skin/icon.png
rename to plugins/npapi/prebuilt/gwtdmp/gwt32.png
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp/gwt48.png b/plugins/npapi/prebuilt/gwtdmp/gwt48.png
new file mode 100644
index 0000000..e8a4172
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp/gwt48.png
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp/gwt64.png b/plugins/npapi/prebuilt/gwtdmp/gwt64.png
new file mode 100644
index 0000000..922cc88
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp/gwt64.png
Binary files differ
diff --git a/plugins/npapi/prebuilt/gwtdmp/manifest.json b/plugins/npapi/prebuilt/gwtdmp/manifest.json
new file mode 100644
index 0000000..ad68f94
--- /dev/null
+++ b/plugins/npapi/prebuilt/gwtdmp/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "GWT Development Mode Plugin",
+  "version": "0.9.0",
+  "description": "A plugin to enable debugging with GWT's Development Mode",
+  "icons": {
+    "16": "gwt16.png",
+    "32": "gwt32.png",
+    "48": "gwt48.png",
+    "64": "gwt64.png",
+    "128": "gwt128.png"
+  },
+  "plugins": [
+    { "path": "WINNT_x86-msvc/npOOPHM.dll", "public": true }
+  ]
+}
diff --git a/plugins/npapi/prebuilt/oophm.xpi b/plugins/npapi/prebuilt/oophm.xpi
deleted file mode 100644
index 425cdba..0000000
--- a/plugins/npapi/prebuilt/oophm.xpi
+++ /dev/null
Binary files differ
diff --git a/plugins/npapi/prebuilt/winnt_x86-msvc/npOOPHM.dll b/plugins/npapi/prebuilt/winnt_x86-msvc/npOOPHM.dll
deleted file mode 100644
index 8e91518..0000000
--- a/plugins/npapi/prebuilt/winnt_x86-msvc/npOOPHM.dll
+++ /dev/null
Binary files differ
diff --git a/plugins/npapi/resource.h b/plugins/npapi/resource.h
index 359695e..645e017 100644
--- a/plugins/npapi/resource.h
+++ b/plugins/npapi/resource.h
@@ -1,20 +1,23 @@
-//{{NO_DEPENDENCIES}}
-// Microsoft Visual C++ generated include file.
-// Used by npApuProto.rc
-//
-#define IDD_MAIN                        101
-#define IDC_BUTTON_GO                   1002
-#define IDC_STATIC_UA                   1003
-#define IDC_BUTTON1                     1005
-#define IDC_BUTTON_DONT                 1005
-
-// Next default values for new objects
-//
-#ifdef APSTUDIO_INVOKED
-#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE        102
-#define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1006
-#define _APS_NEXT_SYMED_VALUE           101
-#endif
-#endif
+//{{NO_DEPENDENCIES}}

+// Microsoft Visual C++ generated include file.

+// Used by npOOPHM.rc

+//

+#define IDD_MAIN                        101

+#define IDC_BUTTON_GO                   1002

+#define IDC_STATIC_UA                   1003

+#define IDC_BUTTON1                     1005

+#define IDC_BUTTON_DONT                 1005

+#define IDD_ALLOW_DIALOG                103

+#define IDC_REMEMBER_CHECKBOX           201

+#define IDC_ALLOW_BUTTON                202

+

+// Next default values for new objects

+//

+#ifdef APSTUDIO_INVOKED

+#ifndef APSTUDIO_READONLY_SYMBOLS

+#define _APS_NEXT_RESOURCE_VALUE        102

+#define _APS_NEXT_COMMAND_VALUE         40001

+#define _APS_NEXT_CONTROL_VALUE         1006

+#define _APS_NEXT_SYMED_VALUE           101

+#endif

+#endif

diff --git a/plugins/ie/oophm/oophm/AllowDialog.cpp b/plugins/platform/Win/AllowDialog.cpp
similarity index 100%
rename from plugins/ie/oophm/oophm/AllowDialog.cpp
rename to plugins/platform/Win/AllowDialog.cpp
diff --git a/plugins/ie/oophm/oophm/AllowDialog.h b/plugins/platform/Win/AllowDialog.h
similarity index 92%
rename from plugins/ie/oophm/oophm/AllowDialog.h
rename to plugins/platform/Win/AllowDialog.h
index b8d14c8..93f9438 100644
--- a/plugins/ie/oophm/oophm/AllowDialog.h
+++ b/plugins/platform/Win/AllowDialog.h
@@ -16,7 +16,10 @@
  * the License.
  */
 
-#include "stdafx.h"
+#ifdef _WINDOWS
+#include <windows.h>
+#include <winnt.h>
+#include <windef.h>
 
 class AllowDialog {
 public:
@@ -34,5 +37,6 @@
 private:
   static HINSTANCE hInstance;
 };
+#endif
 
 #endif
diff --git a/plugins/ie/oophm/oophm/Preferences.cpp b/plugins/platform/Win/Preferences.cpp
similarity index 98%
rename from plugins/ie/oophm/oophm/Preferences.cpp
rename to plugins/platform/Win/Preferences.cpp
index cabe0d5..ee15029 100644
--- a/plugins/ie/oophm/oophm/Preferences.cpp
+++ b/plugins/platform/Win/Preferences.cpp
@@ -14,7 +14,7 @@
  * the License.
  */
 
-#include "stdafx.h"
+#include <windows.h>
 #include <winnt.h>
 #include <winreg.h>
 #include "Debug.h"
diff --git a/plugins/ie/oophm/oophm/Preferences.h b/plugins/platform/Win/Preferences.h
similarity index 100%
rename from plugins/ie/oophm/oophm/Preferences.h
rename to plugins/platform/Win/Preferences.h
diff --git a/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java b/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java
new file mode 100644
index 0000000..e12b1b3
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 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.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
+
+public class SplitLayoutPanelExample implements EntryPoint {
+
+  public void onModuleLoad() {
+    // Create a three-pane layout with splitters. 
+    SplitLayoutPanel p = new SplitLayoutPanel();
+    p.add(new HTML("navigation"), Direction.WEST, 128);
+    p.add(new HTML("list"), Direction.NORTH, 384);
+    p.add(new HTML("details"), Direction.CENTER, 0);
+
+    // Note the explicit call to layout(). This is required for the layout to
+    // take effect.
+    p.layout();
+
+    // Attach the LayoutPanel to the RootLayoutPanel. The latter will listen for
+    // resize events on the window to ensure that its children are informed of
+    // possible size changes.
+    RootLayoutPanel rp = RootLayoutPanel.get();
+    rp.add(p);
+
+    // The RootLayoutPanel also requires that its layout() method be explicitly
+    // called for the initial layout to take effect.
+    rp.layout();
+  }
+}
diff --git a/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java b/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java
new file mode 100644
index 0000000..b493b01
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 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.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.StackLayoutPanel;
+
+public class StackLayoutPanelExample implements EntryPoint {
+
+  public void onModuleLoad() {
+    // Create a three-item stack, with headers sized in EMs. 
+    StackLayoutPanel p = new StackLayoutPanel(Unit.EM);
+    p.add(new HTML("this"), new HTML("[this]"), 128);
+    p.add(new HTML("that"), new HTML("[that]"), 384);
+    p.add(new HTML("the other"), new HTML("[the other]"), 0);
+
+    // Note the explicit call to layout(). This is required for the layout to
+    // take effect.
+    p.layout();
+
+    // Attach the LayoutPanel to the RootLayoutPanel. The latter will listen for
+    // resize events on the window to ensure that its children are informed of
+    // possible size changes.
+    RootLayoutPanel rp = RootLayoutPanel.get();
+    rp.add(p);
+
+    // The RootLayoutPanel also requires that its layout() method be explicitly
+    // called for the initial layout to take effect.
+    rp.layout();
+  }
+}
diff --git a/user/src/com/google/gwt/jsonp/client/JsonpRequest.java b/user/src/com/google/gwt/jsonp/client/JsonpRequest.java
index 43699ec..7d64af1 100644
--- a/user/src/com/google/gwt/jsonp/client/JsonpRequest.java
+++ b/user/src/com/google/gwt/jsonp/client/JsonpRequest.java
@@ -142,7 +142,6 @@
     script.setType("text/javascript");
     script.setId(callbackId);
     script.setSrc(uri.toString());
-    getHeadElement().appendChild(script);
     timer = new Timer() {
       @Override
       public void run() {
@@ -150,6 +149,7 @@
       }
     };
     timer.schedule(timeout);
+    getHeadElement().appendChild(script);
   }
 
   @SuppressWarnings("unused") // used by JSNI
diff --git a/user/src/com/google/gwt/resources/css/ExtractClassNamesVisitor.java b/user/src/com/google/gwt/resources/css/ExtractClassNamesVisitor.java
index c01464f..ca85293 100644
--- a/user/src/com/google/gwt/resources/css/ExtractClassNamesVisitor.java
+++ b/user/src/com/google/gwt/resources/css/ExtractClassNamesVisitor.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2009 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
@@ -29,6 +29,15 @@
  * Collect all CSS class names in a stylesheet.
  */
 public class ExtractClassNamesVisitor extends CssVisitor {
+  /**
+   * Extract all CSS class names in the provided stylesheet.
+   */
+  public static Set<String> exec(CssStylesheet sheet) {
+    ExtractClassNamesVisitor v = new ExtractClassNamesVisitor();
+    v.accept(sheet);
+    return v.found;
+  }
+
   private final Set<String> found = new HashSet<String>();
 
   /**
@@ -51,17 +60,8 @@
   }
 
   /**
-   * Extract all CSS class names in the provided stylesheet.
-   */
-  public Set<String> exec(CssStylesheet sheet) {
-    ExtractClassNamesVisitor v = new ExtractClassNamesVisitor();
-    v.accept(sheet);
-    return v.found;
-  }
-
-  /**
    * Package-protected for testing.
-   * 
+   *
    * @return
    */
   Set<String> getFoundClasses() {
diff --git a/user/src/com/google/gwt/resources/rg/Counter.java b/user/src/com/google/gwt/resources/rg/Counter.java
new file mode 100644
index 0000000..6209605
--- /dev/null
+++ b/user/src/com/google/gwt/resources/rg/Counter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2009 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.resources.rg;
+
+/**
+ * Simple serial number dispenser, handy for hanging off
+ * of caches.
+ */
+class Counter {
+  private int i;
+
+  int next() {
+    return i++;
+  }
+}
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index 307fb1c..8295ad6 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -331,6 +331,7 @@
     }
   }
 
+  @SuppressWarnings("serial")
   static class JClassOrderComparator implements Comparator<JClassType>,
       Serializable {
     public int compare(JClassType o1, JClassType o2) {
@@ -1008,6 +1009,8 @@
   private static final String KEY_BY_CLASS_AND_METHOD = "classAndMethod";
   private static final String KEY_HAS_CACHED_DATA = "hasCachedData";
   private static final String KEY_SHARED_METHODS = "sharedMethods";
+  private static final String KEY_CLASS_PREFIX = "prefix";
+  private static final String KEY_CLASS_COUNTER = "counter";
 
   public static void main(String[] args) {
     for (int i = 0; i < 1000; i++) {
@@ -1083,7 +1086,7 @@
      * Very large concatenation expressions using '+' cause the GWT compiler to
      * overflow the stack due to deep AST nesting. The workaround for now is to
      * force it to be more balanced using intermediate concatenation groupings.
-     * 
+     *
      * This variable is used to track the number of subexpressions within the
      * current parenthetical expression.
      */
@@ -1156,7 +1159,7 @@
   /**
    * Check if number of concat expressions currently exceeds limit and either
    * append '+' if the limit isn't reached or ') + (' if it is.
-   * 
+   *
    * @return numExpressions + 1 or 0 if limit was exceeded.
    */
   private static int concatOp(int numExpressions, StringBuilder b) {
@@ -1239,7 +1242,7 @@
     }
   }
 
-  private String classPrefix;
+  private Counter classCounter;
   private JClassType cssResourceType;
   private JClassType elementType;
   private boolean enableMerge;
@@ -1323,6 +1326,7 @@
   @Override
   public void init(TreeLogger logger, ResourceContext context)
       throws UnableToCompleteException {
+    String classPrefix;
     try {
       PropertyOracle propertyOracle = context.getGeneratorContext().getPropertyOracle();
       ConfigurationProperty styleProp = propertyOracle.getConfigurationProperty("CssResource.style");
@@ -1336,17 +1340,10 @@
       ConfigurationProperty classPrefixProp = propertyOracle.getConfigurationProperty("CssResource.obfuscationPrefix");
       classPrefix = classPrefixProp.getValues().get(0);
     } catch (BadPropertyValueException e) {
-      logger.log(TreeLogger.WARN, "Unable to query module property", e);
+      logger.log(TreeLogger.ERROR, "Unable to query module property", e);
       throw new UnableToCompleteException();
     }
 
-    if ("default".equals(classPrefix)) {
-      // Compute it later in computeObfuscatedNames();
-      classPrefix = null;
-    } else if ("empty".equals(classPrefix)) {
-      classPrefix = "";
-    }
-
     // Find all of the types that we care about in the type system
     TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle();
 
@@ -1361,7 +1358,7 @@
 
     stylesheetMap = new IdentityHashMap<JMethod, CssStylesheet>();
 
-    initReplacements(logger, context);
+    initReplacements(logger, context, classPrefix);
   }
 
   @Override
@@ -1388,17 +1385,19 @@
     (new RequirementsCollector(logger, requirements)).accept(sheet);
   }
 
-  /**
-   * Each distinct type of CssResource has a unique collection of values that it
-   * will return, excepting for those methods that are defined within an
-   * interface that is tagged with {@code @Shared}.
-   */
-  private void computeObfuscatedNames(TreeLogger logger) {
-    logger = logger.branch(TreeLogger.DEBUG, "Computing CSS class replacements");
-
-    SortedSet<JClassType> cssResourceSubtypes = computeOperableTypes(logger);
+  private String computeClassPrefix(String classPrefix,
+      SortedSet<JClassType> cssResourceSubtypes) {
+    if ("default".equals(classPrefix)) {
+      classPrefix = null;
+    } else if ("empty".equals(classPrefix)) {
+      classPrefix = "";
+    }
 
     if (classPrefix == null) {
+      /*
+       * Note that the checksum will miss some or all of the subtypes generated
+       * by other generators.
+       */
       Adler32 checksum = new Adler32();
       for (JClassType type : cssResourceSubtypes) {
         checksum.update(Util.getBytes(type.getQualifiedSourceName()));
@@ -1407,8 +1406,23 @@
           + Long.toString(checksum.getValue(), Character.MAX_RADIX);
     }
 
-    int count = 0;
+    return classPrefix;
+  }
+
+  /**
+   * Each distinct type of CssResource has a unique collection of values that it
+   * will return, excepting for those methods that are defined within an
+   * interface that is tagged with {@code @Shared}.
+   */
+  private void computeObfuscatedNames(TreeLogger logger, String classPrefix,
+      Set<JClassType> cssResourceSubtypes) {
+    logger = logger.branch(TreeLogger.DEBUG, "Computing CSS class replacements");
+
     for (JClassType type : cssResourceSubtypes) {
+      if (replacementsByClassAndMethod.containsKey(type)) {
+        continue;
+      }
+
       Map<JMethod, String> replacements = new IdentityHashMap<JMethod, String>();
       replacementsByClassAndMethod.put(type, replacements);
 
@@ -1425,13 +1439,11 @@
           name = classNameOverride.value();
         }
 
-        String obfuscatedClassName;
+        String obfuscatedClassName = classPrefix + makeIdent(classCounter.next());
         if (prettyOutput) {
-          obfuscatedClassName = classPrefix + "-"
+          obfuscatedClassName += "-"
               + type.getQualifiedSourceName().replaceAll("[.$]", "-") + "-"
               + name;
-        } else {
-          obfuscatedClassName = classPrefix + makeIdent(count++);
         }
 
         replacements.put(method, obfuscatedClassName);
@@ -1550,26 +1562,43 @@
    * names.
    */
   @SuppressWarnings("unchecked")
-  private void initReplacements(TreeLogger logger, ResourceContext context) {
-    if (context.getCachedData(KEY_HAS_CACHED_DATA, Boolean.class) == Boolean.TRUE) {
-      replacementsByClassAndMethod = context.getCachedData(
-          KEY_BY_CLASS_AND_METHOD, Map.class);
-      replacementsForSharedMethods = context.getCachedData(KEY_SHARED_METHODS,
-          Map.class);
-      logger.log(TreeLogger.DEBUG, "Using cached replacements maps");
+  private void initReplacements(TreeLogger logger, ResourceContext context,
+      String classPrefix) {
+    /*
+     * This code was originally written to take a snapshot of all the
+     * CssResource descendants in the TypeOracle on its first run and calculate
+     * the obfuscated names in one go, to ensure that the same obfuscation would
+     * result regardless of the order in which the generators fired. (It no
+     * longer behaves that way, as that scheme prevented the generation of new
+     * CssResource interfaces, but the complexity lives on.)
+     *
+     * TODO(rjrjr,bobv) These days scottb tells us we're guaranteed that the
+     * recompiling the same code will fire the generators in a consistent order,
+     * so the old gymnastics aren't really justified anyway. It would probably
+     * be be worth the effort to simplify this.
+     */
 
-    } else {
-      context.putCachedData(KEY_HAS_CACHED_DATA, Boolean.TRUE);
+    SortedSet<JClassType> cssResourceSubtypes = computeOperableTypes(logger);
 
-      replacementsByClassAndMethod = new IdentityHashMap<JClassType, Map<JMethod, String>>();
+    if (context.getCachedData(KEY_HAS_CACHED_DATA, Boolean.class) != Boolean.TRUE) {
+      context.putCachedData(KEY_CLASS_COUNTER, new Counter());
       context.putCachedData(KEY_BY_CLASS_AND_METHOD,
-          replacementsByClassAndMethod);
-
-      replacementsForSharedMethods = new IdentityHashMap<JMethod, String>();
-      context.putCachedData(KEY_SHARED_METHODS, replacementsForSharedMethods);
-
-      computeObfuscatedNames(logger);
+          new IdentityHashMap<JClassType, Map<JMethod, String>>());
+      context.putCachedData(KEY_SHARED_METHODS,
+          new IdentityHashMap<JMethod, String>());
+      context.putCachedData(KEY_CLASS_PREFIX, computeClassPrefix(classPrefix,
+          cssResourceSubtypes));
+      context.putCachedData(KEY_HAS_CACHED_DATA, Boolean.TRUE);
     }
+
+    classCounter = context.getCachedData(KEY_CLASS_COUNTER, Counter.class);
+    replacementsByClassAndMethod = context.getCachedData(
+        KEY_BY_CLASS_AND_METHOD, Map.class);
+    replacementsForSharedMethods = context.getCachedData(KEY_SHARED_METHODS,
+        Map.class);
+    classPrefix = context.getCachedData(KEY_CLASS_PREFIX, String.class);
+
+    computeObfuscatedNames(logger, classPrefix, cssResourceSubtypes);
   }
 
   private boolean isStrict(TreeLogger logger, ResourceContext context,
@@ -1628,7 +1657,7 @@
   /**
    * Create a Java expression that evaluates to the string representation of the
    * stylesheet resource.
-   * 
+   *
    * @param actualReplacements An out parameter that will be populated by the
    *          obfuscated class names that should be used for the particular
    *          instance of the CssResource, based on any substitution
diff --git a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
index 826edcc..ebb4fce 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
@@ -54,7 +54,8 @@
     final Map<String, JParameter> unfilledRequiredParams =
         new HashMap<String, JParameter>();
 
-    final OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type);
+    final OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type,
+        writer.getLogger());
 
     // See if there's a factory method
     JAbstractMethod creator = writer.getOwnerClass().getUiFactoryMethod(type);
@@ -142,7 +143,7 @@
         JParameter[] params = setter.getParameters();
 
         AttributeParser parser = writer.getAttributeParser(attribute, params);
-        
+
         if (parser == null) {
           writer.die("In %s, unable to parse %s.", elem, attribute);
         }
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
index 0596583..291368f 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -15,9 +15,7 @@
  */
 package com.google.gwt.uibinder.rebind;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JType;
 
@@ -38,12 +36,14 @@
   private final Set<FieldWriter> needs = new HashSet<FieldWriter>();
   private String initializer;
   private boolean written;
+  private MortalLogger logger;
 
-  public AbstractFieldWriter(String name) {
+  public AbstractFieldWriter(String name, MortalLogger logger) {
     if (name == null) {
       throw new RuntimeException("name cannot be null");
     }
     this.name = name;
+    this.logger = logger;
   }
 
   public void needs(FieldWriter f) {
@@ -69,7 +69,7 @@
     this.initializer = initializer;
   }
 
-  public void write(IndentedWriter w, TreeLogger logger)
+  public void write(IndentedWriter w)
       throws UnableToCompleteException {
     if (written) {
       return;
@@ -80,7 +80,7 @@
       // we support more interesting contexts (e.g. the same need being used
       // inside two different
       // LazyPanels)
-      f.write(w, logger);
+      f.write(w);
     }
 
     if (initializer == null) {
@@ -88,9 +88,8 @@
       if (type != null) {
         if ((type.isInterface() == null)
             && (type.findConstructor(new JType[0]) == null)) {
-          logger.log(Type.ERROR, String.format(NO_DEFAULT_CTOR_ERROR,
-              type.getQualifiedSourceName(), type.getName()));
-          throw new UnableToCompleteException();
+          logger.die(NO_DEFAULT_CTOR_ERROR,
+              type.getQualifiedSourceName(), type.getName());
         }
       }
     }
diff --git a/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
index a3a6922..5187e6a 100644
--- a/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
@@ -15,31 +15,47 @@
  */
 package com.google.gwt.uibinder.rebind;
 
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.CssResource.Strict;
-import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
-import com.google.gwt.uibinder.rebind.model.CssResourceGetter;
-
-import java.io.PrintWriter;
+import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
+import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
 
 /**
- * Writes source implementing a {@link ImplicitBundle} to a {@link PrintWriter}.
+ * Writes source implementing an {@link ImplicitClientBundle}.
  */
 public class BundleWriter {
 
-  private final ImplicitBundle bundleClass;
+  private final ImplicitClientBundle bundleClass;
   private final IndentedWriter writer;
+  private final PrintWriterManager writerManager;
   private final TypeOracle oracle;
+  private final String clientBundleType;
+  private final String strictAnnotationType;
 
-  public BundleWriter(ImplicitBundle bundleClass, PrintWriter printWriter,
-      TypeOracle oracle) {
+  public BundleWriter(ImplicitClientBundle bundleClass,
+      PrintWriterManager writerManager, TypeOracle oracle,
+      PrintWriterManager printWriterProvider) {
     this.bundleClass = bundleClass;
-    this.writer = new IndentedWriter(printWriter);
+    this.writer = new IndentedWriter(
+        writerManager.makePrintWriterFor(bundleClass.getClassName()));
+    this.writerManager = writerManager;
     this.oracle = oracle;
+
+     clientBundleType = oracle.findType(ClientBundle.class.getName()).getQualifiedSourceName();
+     strictAnnotationType = oracle.findType(Strict.class.getCanonicalName()).getQualifiedSourceName();
   }
 
-  public void write() {
+  public void write() throws UnableToCompleteException {
+    writeBundleClass();
+    for (ImplicitCssResource css : bundleClass.getCssMethods()) {
+      new CssResourceWriter(css, oracle,
+          writerManager.makePrintWriterFor(css.getClassName())).write();
+    }
+  }
+
+  private void writeBundleClass() {
     // Package declaration
     String packageName = bundleClass.getPackageName();
     if (packageName.length() > 0) {
@@ -49,9 +65,8 @@
 
     // Imports
     writer.write("import %s;",
-        oracle.findType(ClientBundle.class.getName()).getQualifiedSourceName());
-    writer.write("import %s;",
-        oracle.findType(Strict.class.getCanonicalName()).getQualifiedSourceName());
+        clientBundleType);
+    writer.write("import %s;", strictAnnotationType);
     writer.newline();
 
     // Open interface
@@ -60,9 +75,9 @@
     writer.indent();
 
     // Write css methods
-    for (CssResourceGetter css : bundleClass.getCssMethods()) {
-      writer.write("@Strict @Source(\"%s\")",css.getSource());
-      writer.write("%s %s();", css.getExtendedInterface().getQualifiedSourceName(), css.getName());
+    for (ImplicitCssResource css : bundleClass.getCssMethods()) {
+      writer.write("@Strict @Source(\"%s\")", css.getSource());
+      writer.write("%s %s();", css.getClassName(), css.getName());
       writer.newline();
     }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/CssResourceWriter.java b/user/src/com/google/gwt/uibinder/rebind/CssResourceWriter.java
new file mode 100644
index 0000000..72d0f0e
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/CssResourceWriter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
+
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * Writes the source to implement an {@link ImplicitCssResource} interface.
+ */
+public class CssResourceWriter {
+  /**
+   *
+   */
+  private static final JType[] NO_PARAMS = new JType[0];
+  private final ImplicitCssResource css;
+  private final IndentedWriter writer;
+  private final JClassType cssResourceType;
+  private final JClassType stringType;
+
+  public CssResourceWriter(ImplicitCssResource css, TypeOracle oracle,
+      PrintWriter writer) {
+    this.css = css;
+    this.writer = new IndentedWriter(writer);
+    this.cssResourceType = oracle.findType(CssResource.class.getName());
+    this.stringType = oracle.findType(String.class.getName());
+  }
+
+  public void write() throws UnableToCompleteException {
+    // Package declaration
+    String packageName = css.getPackageName();
+    if (packageName.length() > 0) {
+      writer.write("package %1$s;", packageName);
+      writer.newline();
+    }
+
+    JClassType superType = css.getExtendedInterface();
+    if (superType == null) {
+      superType = cssResourceType;
+    }
+
+    writer.write("import %s;", superType.getQualifiedSourceName());
+    writer.newline();
+
+    // Open interface
+    writer.write("public interface %s extends %s {", css.getClassName(),
+        superType.getSimpleSourceName());
+    writer.indent();
+
+    writeCssMethods(superType);
+
+    // Close interface.
+    writer.outdent();
+    writer.write("}");
+  }
+
+  private void writeCssMethods(JClassType superType) throws UnableToCompleteException {
+    Set<String> classNames = css.getCssClassNames();
+
+    /*
+     * Only write names that we are not overriding from super, or else we'll
+     * re-obfuscate any @Shared ones
+     */
+    for (String className : classNames) {
+      JMethod method = superType.findMethod(className, NO_PARAMS);
+      if (method == null || !stringType.equals(method.getReturnType())) {
+        writer.write("String %s();", className);
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
index 0624bc5..67418ea 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.uibinder.rebind;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 
@@ -42,13 +41,13 @@
    */
   private final LinkedList<FieldWriter> parsedFieldStack = new LinkedList<FieldWriter>();
 
-  private TreeLogger logger;
+  private MortalLogger logger;
 
   /**
    * Basic constructor just injects an oracle instance.
    */
-  public FieldManager(TreeLogger logger) {
-    this.logger = logger;
+  public FieldManager(MortalLogger logger2) {
+    this.logger = logger2;
   }
 
   /**
@@ -116,7 +115,7 @@
   public FieldWriter registerFieldOfGeneratedType(String typePackage,
       String typeName, String fieldName) throws UnableToCompleteException {
     FieldWriter field = new FieldWriterOfGeneratedType(typePackage, typeName,
-        fieldName);
+        fieldName, logger);
     return registerField(fieldName, field);
   }
 
@@ -130,7 +129,7 @@
       String ownerTypeName) throws UnableToCompleteException {
     Collection<FieldWriter> fields = fieldsMap.values();
     for (FieldWriter field : fields) {
-      field.write(writer, logger);
+      field.write(writer);
     }
   }
 
@@ -148,9 +147,7 @@
 
   private void requireUnique(String fieldName) throws UnableToCompleteException {
     if (fieldsMap.containsKey(fieldName)) {
-      Object[] params = {fieldName};
-      logger.log(TreeLogger.ERROR, String.format(DUPLICATE_FIELD_ERROR, params));
-      throw new UnableToCompleteException();
+      logger.die(DUPLICATE_FIELD_ERROR, fieldName);
     }
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
index 6daf715..a4c2beb 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.uibinder.rebind;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 
@@ -73,9 +72,7 @@
 
   /**
    * Write the field delcaration.
-   *
-   * @param logger
    */
-  void write(IndentedWriter w, TreeLogger logger)
+  void write(IndentedWriter w)
       throws UnableToCompleteException;
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
index 3c32b9b..035b01c 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.uibinder.rebind;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 
 /**
@@ -24,10 +23,10 @@
  */
 class FieldWriterOfExistingType extends AbstractFieldWriter {
   final JClassType type;
-  final TreeLogger logger;
+  final MortalLogger logger;
 
-  FieldWriterOfExistingType(JClassType type, String name, TreeLogger logger) {
-    super(name);
+  FieldWriterOfExistingType(JClassType type, String name, MortalLogger logger) {
+    super(name, logger);
     this.logger = logger;
     if (type == null) {
       throw new RuntimeException("type cannot be null");
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java
index ddefbfb..4204f77 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java
@@ -27,11 +27,8 @@
   private final String typePackage;
   private final String typeName;
 
-  /**
-   * @param name
-   */
-  public FieldWriterOfGeneratedType(String typePackage, String typeName, String name) {
-    super(name);
+  public FieldWriterOfGeneratedType(String typePackage, String typeName, String name, MortalLogger logger) {
+    super(name, logger);
     if (typeName == null) {
       throw new RuntimeException("typeName must not be null");
     }
diff --git a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
index 6c10827..bdea59a 100644
--- a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.uibinder.rebind;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
@@ -33,7 +32,9 @@
  * annotated with {@link com.google.gwt.uibinder.client.UiHandler} so that the
  * user doesn't need to worry about writing code to implement these bindings.
  *
- * <p>For instance, the class defined below:
+ * <p>
+ * For instance, the class defined below:
+ *
  * <pre>
  *   public class MyClass {
  *     @UiField Label label;
@@ -46,6 +47,7 @@
  * </pre>
  *
  * will generate a piece of code like:
+ *
  * <pre>
  *    ClickHandler handler0 = new ClickHandler() {
  *      @Override
@@ -63,12 +65,10 @@
  */
 class HandlerEvaluator {
 
-  private static final String HANDLER_BASE_NAME =
-    "handlerMethodWithNameVeryUnlikelyToCollideWithUserFieldNames";
+  private static final String HANDLER_BASE_NAME = "handlerMethodWithNameVeryUnlikelyToCollideWithUserFieldNames";
   /*
    * TODO(rjrjr) The correct fix is to put the handlers in a locally defined
-   * class, making the generated code look like this:
-   *
+   * class, making the generated code look like this
    *
    * http://docs.google.com/Doc?docid=0AQfnKgX9tAdgZGZ2cTM5YjdfMmQ4OTk0eGhz&hl=en
    *
@@ -77,7 +77,7 @@
    */
   private int varCounter = 0;
 
-  private final TreeLogger logger;
+  private final MortalLogger logger;
 
   private final JClassType handlerRegistrationJClass;
   private final JClassType eventHandlerJClass;
@@ -90,7 +90,7 @@
    * @param logger the logger for warnings and errors
    * @param oracle the type oracle
    */
-  HandlerEvaluator(OwnerClass ownerClass, TreeLogger logger, TypeOracle oracle) {
+  HandlerEvaluator(OwnerClass ownerClass, MortalLogger logger, TypeOracle oracle) {
     this.ownerClass = ownerClass;
     this.logger = logger;
 
@@ -99,17 +99,6 @@
   }
 
   /**
-   * Post an error message and throws an {@link UnableToCompleteException}.
-   *
-   * @param message the error message
-   * @param params the format params
-   */
-  public void die(String message, Object... params) throws UnableToCompleteException {
-    logger.log(TreeLogger.ERROR, String.format(message, params));
-    throw new UnableToCompleteException();
-  }
-
-  /**
    * Runs the evaluator in the given class according to the valid fields
    * extracted from the template (via attribute ui:field).
    *
@@ -125,24 +114,27 @@
       // Evaluate the method.
       String boundMethod = method.getName();
       if (method.isPrivate()) {
-        die("Method '%s' cannot be private.", boundMethod);
+        logger.die("Method '%s' cannot be private.", boundMethod);
       }
 
       // Retrieves both event and handler types.
       JParameter[] parameters = method.getParameters();
       if (parameters.length != 1) {
-        die("Method '%s' must have a single event parameter defined.", boundMethod);
+        logger.die("Method '%s' must have a single event parameter defined.",
+            boundMethod);
       }
       JClassType eventType = parameters[0].getType().isClass();
 
       JClassType handlerType = getHandlerForEvent(eventType);
       if (handlerType == null) {
-        die("Parameter '%s' is not an event (subclass of GwtEvent).", eventType.getName());
+        logger.die("Parameter '%s' is not an event (subclass of GwtEvent).",
+            eventType.getName());
       }
 
       // Cool to add the handler in the output.
       String handlerVarName = HANDLER_BASE_NAME + (++varCounter);
-      writeHandler(writer, uiOwner, handlerVarName, handlerType, eventType, boundMethod);
+      writeHandler(writer, uiOwner, handlerVarName, handlerType, eventType,
+          boundMethod);
 
       // Adds the handler created above.
       UiHandler annotation = method.getAnnotation(UiHandler.class);
@@ -150,36 +142,27 @@
         // Is the field object valid?
         FieldWriter fieldWriter = fieldManager.lookup(objectName);
         if (fieldWriter == null) {
-          die("Method '%s' can not be bound. You probably missed ui:field='%s' "
-              + "in the template.", boundMethod, objectName);
+          logger.die(
+              ("Method '%s' can not be bound. You probably missed ui:field='%s' "
+                  + "in the template."), boundMethod, objectName);
         }
 
         // Retrieves the "add handler" method in the object.
         JMethod addHandlerMethodType = getAddHandlerMethodForObject(
             fieldWriter.getType(), handlerType);
         if (addHandlerMethodType == null) {
-          die("Field '%s' does not have an 'add%s' method associated.",
+          logger.die("Field '%s' does not have an 'add%s' method associated.",
               objectName, handlerType.getName());
         }
 
         // Cool to tie the handler into the object.
-        writeAddHandler(writer, handlerVarName,
-            addHandlerMethodType.getName(), objectName);
+        writeAddHandler(writer, handlerVarName, addHandlerMethodType.getName(),
+            objectName);
       }
     }
   }
 
   /**
-   * Post a warning message.
-   *
-   * @param message the error message
-   * @param params the format params
-   */
-  public void warn(String message, Object... params) {
-    logger.log(TreeLogger.WARN, String.format(message, params));
-  }
-
-  /**
    * Writes a handler entry using the given writer.
    *
    * @param writer the writer used to output the results
@@ -189,30 +172,32 @@
    * @param eventType the event associated with the handler
    * @param boundMethod the method bound in the handler
    */
-  protected void writeHandler(IndentedWriter writer, String uiOwner, String handlerVarName,
-      JClassType handlerType, JClassType eventType, String boundMethod)
-      throws UnableToCompleteException {
+  protected void writeHandler(IndentedWriter writer, String uiOwner,
+      String handlerVarName, JClassType handlerType, JClassType eventType,
+      String boundMethod) throws UnableToCompleteException {
 
     // Retrieves the single method (usually 'onSomething') related to all
     // handlers. Ex: onClick in ClickHandler, onBlur in BlurHandler ...
     JMethod[] methods = handlerType.getMethods();
     if (methods.length != 1) {
-      die("'%s' has more than one method defined.", handlerType.getName());
+      logger.die("'%s' has more than one method defined.",
+          handlerType.getName());
     }
 
     // Checks if the method has an Event as parameter. Ex: ClickEvent in
     // onClick, BlurEvent in onBlur ...
     JParameter[] parameters = methods[0].getParameters();
     if (parameters.length != 1 || parameters[0].getType() != eventType) {
-      die("Method '%s' needs '%s' as parameter", methods[0].getName(), eventType.getName());
+      logger.die("Method '%s' needs '%s' as parameter", methods[0].getName(),
+          eventType.getName());
     }
 
     writer.newline();
     writer.write("final %1$s %2$s = new %1$s() {",
         handlerType.getParameterizedQualifiedSourceName(), handlerVarName);
     writer.indent();
-    writer.write("public void %1$s(%2$s event) {",
-        methods[0].getName(), eventType.getParameterizedQualifiedSourceName());
+    writer.write("public void %1$s(%2$s event) {", methods[0].getName(),
+        eventType.getParameterizedQualifiedSourceName());
     writer.indent();
     writer.write("%1$s.%2$s(event);", uiOwner, boundMethod);
     writer.outdent();
@@ -226,14 +211,14 @@
    *
    * @param writer the writer used to output the results
    * @param handlerVarName the name of the handler variable
-   * @param addHandlerMethodName the "add handler" method name associated
-   *        with the object
+   * @param addHandlerMethodName the "add handler" method name associated with
+   *          the object
    * @param objectName the name of the object we want to tie the handler
    */
   void writeAddHandler(IndentedWriter writer, String handlerVarName,
       String addHandlerMethodName, String objectName) {
-    writer.write("%1$s.%2$s(%3$s);", objectName,
-        addHandlerMethodName, handlerVarName);
+    writer.write("%1$s.%2$s(%3$s);", objectName, addHandlerMethodName,
+        handlerVarName);
   }
 
   /**
@@ -242,8 +227,10 @@
    * {@link com.google.gwt.event.shared.HandlerRegistration} and receives a
    * single input parameter of the same type of handlerType.
    *
-   * <p>Output an error in case more than one method match the conditions
-   * described above.</p>
+   * <p>
+   * Output an error in case more than one method match the conditions described
+   * above.
+   * </p>
    *
    * <pre>
    *   <b>Examples:</b>
@@ -256,11 +243,11 @@
    * @param objectType the object type we want to check
    * @param handlerType the handler type we want to check in the object
    *
-   * @return the method that adds handlerType into objectType, or <b>null</b>
-   *         if no method was found
+   * @return the method that adds handlerType into objectType, or <b>null</b> if
+   *         no method was found
    */
   private JMethod getAddHandlerMethodForObject(JClassType objectType,
-        JClassType handlerType) throws UnableToCompleteException {
+      JClassType handlerType) throws UnableToCompleteException {
     JMethod handlerMethod = null;
     for (JMethod method : objectType.getOverridableMethods()) {
 
@@ -269,12 +256,14 @@
 
         // Condition 2: single parameter of the same type of handlerType?
         JParameter[] parameters = method.getParameters();
-        if ((parameters.length == 1) && handlerType.equals(parameters[0].getType())) {
+        if ((parameters.length == 1)
+            && handlerType.equals(parameters[0].getType())) {
 
           // Condition 3: does more than one method match the condition?
           if (handlerMethod != null) {
-            die("This handler cannot be generated. Methods '%s' and '%s' are "
-                + "ambiguous. Which one to pick?", method, handlerMethod);
+            logger.die(
+                ("This handler cannot be generated. Methods '%s' and '%s' are "
+                    + "ambiguous. Which one to pick?"), method, handlerMethod);
           }
 
           handlerMethod = method;
@@ -295,11 +284,11 @@
     // All handlers event must have an overrided method getAssociatedType().
     // We take advantage of this information to get the associated handler.
     // Ex:
-    //  com.google.gwt.event.dom.client.ClickEvent
-    //    ---> com.google.gwt.event.dom.client.ClickHandler
+    // com.google.gwt.event.dom.client.ClickEvent
+    // ---> com.google.gwt.event.dom.client.ClickHandler
     //
-    //  com.google.gwt.event.dom.client.BlurEvent
-    //    ---> com.google.gwt.event.dom.client.BlurHandler
+    // com.google.gwt.event.dom.client.BlurEvent
+    // ---> com.google.gwt.event.dom.client.BlurHandler
 
     if (eventType == null) {
       return null;
@@ -307,29 +296,34 @@
 
     JMethod method = eventType.findMethod("getAssociatedType", new JType[0]);
     if (method == null) {
-      warn("Method 'getAssociatedType()' could not be found in the event '%s'.",
+      logger.warn(
+          "Method 'getAssociatedType()' could not be found in the event '%s'.",
           eventType.getName());
       return null;
     }
 
     JType returnType = method.getReturnType();
     if (returnType == null) {
-      warn("The method 'getAssociatedType()' in the event '%s' returns void.",
+      logger.warn(
+          "The method 'getAssociatedType()' in the event '%s' returns void.",
           eventType.getName());
       return null;
     }
 
     JParameterizedType isParameterized = returnType.isParameterized();
     if (isParameterized == null) {
-      warn("The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
-        eventType.getName());
+      logger.warn(
+          "The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
+          eventType.getName());
       return null;
     }
 
     JClassType[] argTypes = isParameterized.getTypeArgs();
-    if ((argTypes.length != 1) && !argTypes[0].isAssignableTo(eventHandlerJClass)) {
-      warn("The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
-        eventType.getName());
+    if ((argTypes.length != 1)
+        && !argTypes[0].isAssignableTo(eventHandlerJClass)) {
+      logger.warn(
+          "The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
+          eventType.getName());
       return null;
     }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java b/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java
new file mode 100644
index 0000000..80964ad
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/MortalLogger.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+/**
+ * Wraps a {@link TreeLogger} with handy {@link String#format} style methods and
+ * can be told to die. Perhaps we should instead add die(), warn(), etc. to
+ * Treelogger.
+ */
+public class MortalLogger {
+  private final TreeLogger logger;
+
+  MortalLogger(TreeLogger logger) {
+    this.logger = logger;
+  }
+
+  /**
+   * Post an error message and halt processing. This method always throws an
+   * {@link UnableToCompleteException}.
+   */
+  public void die(String message, Object... params)
+      throws UnableToCompleteException {
+    logger.log(TreeLogger.ERROR, String.format(message, params));
+    throw new UnableToCompleteException();
+  }
+
+  public TreeLogger getTreeLogger() {
+    return logger;
+  }
+
+  /**
+   * Post a warning message.
+   */
+  public void warn(String message, Object... params) {
+    logger.log(TreeLogger.WARN, String.format(message, params));
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/PrintWriterManager.java b/user/src/com/google/gwt/uibinder/rebind/PrintWriterManager.java
new file mode 100644
index 0000000..1b79e6d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/PrintWriterManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2009 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Factory for printwriters creating source files in a particular package.
+ */
+class PrintWriterManager {
+  private final GeneratorContext genCtx;
+  private final String packageName;
+  private final TreeLogger logger;
+  private final Set<PrintWriter> writers = new HashSet<PrintWriter>();
+
+  PrintWriterManager(GeneratorContext genCtx, TreeLogger logger,
+      String packageName) {
+    this.genCtx = genCtx;
+    this.packageName = packageName;
+    this.logger = logger;
+  }
+
+  /**
+   * Commit all writers we have vended.
+   */
+  void commit() {
+    for (PrintWriter writer : writers) {
+      genCtx.commit(logger, writer);
+    }
+  }
+
+  /**
+   * @param name classname
+   * @return the printwriter
+   * @throws RuntimeException if this class has already been written
+   */
+  PrintWriter makePrintWriterFor(String name) {
+    PrintWriter writer = tryToMakePrintWriterFor(name);
+    if (writer == null) {
+      throw new RuntimeException(String.format("Tried to write %s.%s twice.",
+          packageName, name));
+    }
+    return writer;
+  }
+
+  /**
+   * @param name classname
+   * @return the printwriter, or null if this class has already been written
+   */
+  PrintWriter tryToMakePrintWriterFor(String name) {
+    PrintWriter writer = genCtx.tryCreate(logger, packageName, name);
+    if (writer != null) {
+      writers.add(writer);
+    }
+    return writer;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
index 59cfa4c..7f4045b 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -24,22 +24,9 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.uibinder.client.UiTemplate;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
-import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
+import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
 
-import org.w3c.dom.Document;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import java.io.IOException;
-import java.io.InputStream;
 import java.io.PrintWriter;
-import java.net.URL;
-import java.util.Enumeration;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
 
 /**
  * Generator for implementations of
@@ -49,77 +36,12 @@
 
   private static final String TEMPLATE_SUFFIX = ".ui.xml";
 
-  private static void commit(TreeLogger logger, GeneratorContext genCtx,
-      String packageName, PrintWriter pw, UiBinderWriter uiWriter) {
-    uiWriter.write(pw);
-    genCtx.commit(logger, pw);
-
-    MessagesWriter messages = uiWriter.getMessages();
-    if (messages.hasMessages()) {
-      PrintWriter messagesPrintWriter = genCtx.tryCreate(logger, packageName,
-          messages.getMessagesClassName());
-      if (messagesPrintWriter == null) {
-        throw new RuntimeException("Tried to gen messages twice.");
-      }
-
-      messages.write(messagesPrintWriter);
-      genCtx.commit(logger, messagesPrintWriter);
-    }
-
-    ImplicitBundle bundleClass = uiWriter.getBundleClass();
-    PrintWriter bundlePrintWriter = genCtx.tryCreate(logger, packageName,
-        bundleClass.getClassName());
-    if (bundlePrintWriter == null) {
-      throw new RuntimeException("Tried to gen bundle twice.");
-    }
-
-    new BundleWriter(bundleClass, bundlePrintWriter, genCtx.getTypeOracle()).write();
-    genCtx.commit(logger, bundlePrintWriter);
-  }
-
-  @Override
-  public String generate(TreeLogger logger, GeneratorContext genCtx,
-      String fqInterfaceName) throws UnableToCompleteException {
-    TypeOracle oracle = genCtx.getTypeOracle();
-    JClassType interfaceType;
-    try {
-      interfaceType = oracle.getType(fqInterfaceName);
-    } catch (NotFoundException e) {
-      throw new RuntimeException(e);
-    }
-
-    String implName = interfaceType.getName();
-    implName = implName.replace('.', '_') + "Impl";
-
-    String packageName = interfaceType.getPackage().getName();
-    PrintWriter printWriter = genCtx.tryCreate(logger, packageName, implName);
-
-    if (printWriter != null) {
-      String templateName = deduceTemplateFile(logger, interfaceType);
-
-      Document document;
-      try {
-        document = parseXmlResource(logger, templateName);
-      } catch (SAXParseException e) {
-        logger.log(TreeLogger.ERROR, "Error parsing XML (line "
-            + e.getLineNumber() + "): " + e.getMessage(), e);
-        throw new UnableToCompleteException();
-      }
-
-      UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType,
-          implName, templateName, oracle, logger);
-      uiBinderWriter.parseDocument(document);
-      commit(logger, genCtx, packageName, printWriter, uiBinderWriter);
-    }
-    return packageName + "." + implName;
-  }
-
   /**
    * Given a UiBinder interface, return the path to its ui.xml file, suitable
    * for any classloader to find it as a resource.
    */
-  private String deduceTemplateFile(TreeLogger logger, JClassType interfaceType)
-      throws UnableToCompleteException {
+  private static String deduceTemplateFile(MortalLogger logger,
+      JClassType interfaceType) throws UnableToCompleteException {
     String templateName = null;
     UiTemplate annotation = interfaceType.getAnnotation(UiTemplate.class);
     if (annotation == null) {
@@ -132,9 +54,8 @@
     } else {
       templateName = annotation.value();
       if (!templateName.endsWith(TEMPLATE_SUFFIX)) {
-        logger.log(TreeLogger.ERROR, "Template file name must end with "
+        logger.die("Template file name must end with "
             + TEMPLATE_SUFFIX);
-        throw new UnableToCompleteException();
       }
 
       /*
@@ -153,48 +74,54 @@
     return templateName;
   }
 
-  private Document parseXmlResource(TreeLogger logger, final String resourcePath)
-      throws SAXParseException, UnableToCompleteException {
-    // Get the document builder. We need namespaces, and automatic expanding
-    // of entity references (the latter of which makes life somewhat easier
-    // for XMLElement).
-    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-    factory.setNamespaceAware(true);
-    factory.setExpandEntityReferences(true);
-    DocumentBuilder builder;
-    try {
-      builder = factory.newDocumentBuilder();
-    } catch (ParserConfigurationException e) {
-      throw new RuntimeException(e);
-    }
-
-    try {
-      ClassLoader classLoader = UiBinderGenerator.class.getClassLoader();
-      Enumeration<URL> urls = classLoader.getResources(resourcePath);
-      if (!urls.hasMoreElements()) {
-        logger.log(TreeLogger.ERROR, "Unable to find resource: " + resourcePath);
-        throw new UnableToCompleteException();
-      }
-      URL url = urls.nextElement();
-
-      InputStream stream = url.openStream();
-      InputSource input = new InputSource(stream);
-      input.setSystemId(url.toExternalForm());
-
-      builder.setEntityResolver(new GwtResourceEntityResolver());
-
-      return builder.parse(input);
-    } catch (SAXParseException e) {
-      // Let SAXParseExceptions through.
-      throw e;
-    } catch (SAXException e) {
-      throw new RuntimeException(e);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
+  private static String slashify(String s) {
+    return s.replace(".", "/");
   }
 
-  private String slashify(String s) {
-    return s.replace(".", "/");
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext genCtx,
+      String fqInterfaceName) throws UnableToCompleteException {
+    TypeOracle oracle = genCtx.getTypeOracle();
+    JClassType interfaceType;
+    try {
+      interfaceType = oracle.getType(fqInterfaceName);
+    } catch (NotFoundException e) {
+      throw new RuntimeException(e);
+    }
+
+    String implName = interfaceType.getName().replace('.', '_') + "Impl";
+    String packageName = interfaceType.getPackage().getName();
+    PrintWriterManager writers = new PrintWriterManager(genCtx, logger,
+        packageName);
+    PrintWriter printWriter = writers.tryToMakePrintWriterFor(implName);
+
+    if (printWriter != null) {
+      generateOnce(interfaceType, implName, packageName, printWriter, logger,
+          oracle, writers);
+    }
+    return packageName + "." + implName;
+  }
+
+  private void generateOnce(JClassType interfaceType, String implName,
+      String packageName, PrintWriter binderPrintWrier, TreeLogger treeLogger,
+      TypeOracle oracle, PrintWriterManager writerManager)
+      throws UnableToCompleteException {
+
+    MortalLogger logger = new MortalLogger(treeLogger);
+    String templatePath = deduceTemplateFile(logger, interfaceType);
+
+    UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType, implName,
+        templatePath, oracle, logger);
+    uiBinderWriter.parseDocument(binderPrintWrier);
+
+    MessagesWriter messages = uiBinderWriter.getMessages();
+    if (messages.hasMessages()) {
+      messages.write(writerManager.makePrintWriterFor(messages.getMessagesClassName()));
+    }
+
+    ImplicitClientBundle bundleClass = uiBinderWriter.getBundleClass();
+    new BundleWriter(bundleClass, writerManager, oracle, writerManager).write();
+
+    writerManager.commit();
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index 100fd0b..f26d9ca 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -20,9 +20,10 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.parsers.NullInterpreter;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
-import com.google.gwt.uibinder.rebind.model.CssResourceGetter;
-import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
+import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
+import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
 import com.google.gwt.uibinder.rebind.model.OwnerField;
 
 /**
@@ -38,16 +39,19 @@
   private final TypeOracle oracle;
   private final MessagesWriter messagesWriter;
   private final FieldManager fieldManager;
-  private final ImplicitBundle bundleClass;
+  private final ImplicitClientBundle bundleClass;
+  private final JClassType cssResourceType;
 
   public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter,
-      FieldManager fieldManager, TypeOracle oracle, ImplicitBundle bundleClass) {
+      FieldManager fieldManager, TypeOracle oracle,
+      ImplicitClientBundle bundleClass) {
     this.writer = writer;
     this.oracle = oracle;
     this.messagesWriter = messagesWriter;
     this.fieldManager = fieldManager;
     this.bundleClass = bundleClass;
-  }
+    this.cssResourceType = oracle.findType(CssResource.class.getCanonicalName());
+ }
 
   /**
    * Parses the root UiBinder element, and kicks off the parsing of the rest of
@@ -55,7 +59,7 @@
    */
   public String parse(XMLElement elem) throws UnableToCompleteException {
     // TODO(rjrjr) Clearly need to break these find* methods out into their own
-    // parsers, need registration scheme for ui binder's own parsers
+    // parsers, an so need a registration scheme for uibinder-specific parsers
     findStyles(elem);
     findResources(elem);
     messagesWriter.findMessagesConfig(elem);
@@ -65,10 +69,19 @@
 
   private JClassType consumeCssResourceType(XMLElement elem)
       throws UnableToCompleteException {
-    JClassType publicType = consumeTypeAttribute(elem);
-    JClassType cssResourceType = oracle.findType(CssResource.class.getCanonicalName());
+    String typeName = elem.consumeAttribute("type", null);
+    if (typeName == null) {
+      return cssResourceType;
+    }
+
+    JClassType publicType = oracle.findType(typeName);
+    if (publicType == null) {
+      writer.die("In %s, no such type %s", elem, typeName);
+    }
+
     if (!publicType.isAssignableTo(cssResourceType)) {
-      writer.die("In %s, type %s does not extend %s", elem, publicType.getQualifiedSourceName(),
+      writer.die("In %s, type %s does not extend %s", elem,
+          publicType.getQualifiedSourceName(),
           cssResourceType.getQualifiedSourceName());
     }
     return publicType;
@@ -129,14 +142,20 @@
   }
 
   private void createStyle(XMLElement elem) throws UnableToCompleteException {
-    // Won't be required for long
-    String source = elem.consumeRequiredAttribute("source");
+    String body =  elem.consumeInnerText(new NullInterpreter<String>());
+    if (body.length() > 0 && elem.hasAttribute("source")) {
+      writer.die("In %s, cannot use both a source attribute and inline css text.", elem);
+    }
+
+    String source = elem.consumeAttribute("source");
     String name = elem.consumeAttribute("field", "style");
     JClassType publicType = consumeCssResourceType(elem);
 
-    CssResourceGetter cssMethod = bundleClass.createCssResource(name, source,
-        publicType);
-    FieldWriter field = fieldManager.registerField(publicType,
+    ImplicitCssResource cssMethod = bundleClass.createCssResource(name, source,
+        publicType, body);
+
+    FieldWriter field = fieldManager.registerFieldOfGeneratedType(
+        cssMethod.getPackageName(), cssMethod.getClassName(),
         cssMethod.getName());
     field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(),
         cssMethod.getName()));
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 9482309..edec5f7 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.uibinder.rebind;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
@@ -31,16 +30,22 @@
 import com.google.gwt.uibinder.parsers.ElementParser;
 import com.google.gwt.uibinder.parsers.StrictAttributeParser;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
-import com.google.gwt.uibinder.rebind.model.CssResourceGetter;
-import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
+import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
+import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
 import com.google.gwt.uibinder.rebind.model.OwnerClass;
 import com.google.gwt.uibinder.rebind.model.OwnerField;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -48,6 +53,10 @@
 import java.util.List;
 import java.util.Map;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
 /**
  * Writer for UiBinder generated classes.
  *
@@ -178,6 +187,8 @@
     return list;
   }
 
+  private final MortalLogger logger;
+
   /**
    * Class names of parsers for values of attributes with no namespace prefix,
    * keyed by method parameter signatures.
@@ -203,7 +214,6 @@
   private final MessagesWriter messages;
   private final Tokenator tokenator = new Tokenator();
 
-  private final TreeLogger logger;
   private final String templatePath;
   private final TypeOracle oracle;
   /**
@@ -223,7 +233,7 @@
 
   private final FieldManager fieldManager;
 
-  private final ImplicitBundle bundleClass;
+  private final ImplicitClientBundle bundleClass;
 
   private int fieldIndex;
 
@@ -232,13 +242,14 @@
   private String rendered;
 
   UiBinderWriter(JClassType baseClass, String implClassName,
-      String templatePath, TypeOracle oracle, TreeLogger logger)
+      String templatePath, TypeOracle oracle, MortalLogger logger)
       throws UnableToCompleteException {
     this.baseClass = baseClass;
     this.implClassName = implClassName;
     this.oracle = oracle;
     this.logger = logger;
     this.templatePath = templatePath;
+
     this.messages = new MessagesWriter(BINDER_URI, logger, templatePath,
         baseClass.getPackage().getName(), this.implClassName);
 
@@ -247,30 +258,24 @@
     uiRootType = typeArgs[0];
     uiOwnerType = typeArgs[1];
 
-    ownerClass = new OwnerClass(uiOwnerType);
-    bundleClass = new ImplicitBundle(baseClass.getPackage().getName(),
-        this.implClassName, CLIENT_BUNDLE_FIELD);
+    ownerClass = new OwnerClass(uiOwnerType, logger);
+    bundleClass = new ImplicitClientBundle(baseClass.getPackage().getName(),
+        this.implClassName, CLIENT_BUNDLE_FIELD, logger);
     handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
     fieldManager = new FieldManager(logger);
   }
 
   /**
-   * Add a statement to be run after everything has been instantiated.
+   * Add a statement to be run after everything has been instantiated, in the
+   * style of {@link String#format}
    */
   public void addInitStatement(String format, Object... params) {
     initStatements.add(String.format(format, params));
   }
 
   /**
-   * Adds a statement to the block run after fields are declared.
-   */
-  public void addStatement(String statement) {
-    statements.add(statement);
-  }
-
-  /**
-   * Adds a statement to the block run after fields are declared, using the Java
-   * message format.
+   * Adds a statement to the block run after fields are declared, in the style
+   * of {@link String#format}
    */
   public void addStatement(String format, Object... args) {
     statements.add(String.format(format, args));
@@ -372,8 +377,7 @@
    * {@link UnableToCompleteException}
    */
   public void die(String message) throws UnableToCompleteException {
-    logger.log(TreeLogger.ERROR, message);
-    throw new UnableToCompleteException();
+    logger.die(message);
   }
 
   /**
@@ -382,8 +386,7 @@
    */
   public void die(String message, Object... params)
       throws UnableToCompleteException {
-    logger.log(TreeLogger.ERROR, String.format(message, params));
-    throw new UnableToCompleteException();
+    logger.die(message, params);
   }
 
   /**
@@ -505,11 +508,19 @@
     return parser;
   }
 
-  public ImplicitBundle getBundleClass() {
+  public ImplicitClientBundle getBundleClass() {
     return bundleClass;
   }
 
   /**
+   * @return The logger, at least until we get get it handed off to parsers via
+   *         constructor args.
+   */
+  public MortalLogger getLogger() {
+    return logger;
+  }
+
+  /**
    * Get the {@link MessagesWriter} for this UI, generating it if necessary.
    */
   public MessagesWriter getMessages() {
@@ -615,14 +626,14 @@
    * Post a warning message.
    */
   public void warn(String message) {
-    logger.log(TreeLogger.WARN, message);
+    logger.warn(message);
   }
 
   /**
    * Post a warning message.
    */
   public void warn(String message, Object... params) {
-    logger.log(TreeLogger.WARN, String.format(message, params));
+    logger.warn(message, params);
   }
 
   /**
@@ -630,7 +641,15 @@
    * implementation's superstructure, and parses the root widget (leading to all
    * of its children being parsed as well).
    */
-  void parseDocument(Document doc) throws UnableToCompleteException {
+  void parseDocument(PrintWriter printWriter) throws UnableToCompleteException {
+    Document doc = null;
+    try {
+      doc = parseXmlResource(templatePath);
+    } catch (SAXParseException e) {
+      die("Error parsing XML (line " + e.getLineNumber() + "): "
+          + e.getMessage(), e);
+    }
+
     JClassType uiBinderClass = getOracle().findType(UiBinder.class.getName());
     if (!baseClass.isAssignableTo(uiBinderClass)) {
       die(baseClass.getName() + " must implement UiBinder");
@@ -641,9 +660,6 @@
 
     XMLElement elem = new XMLElement(documentElement, this);
     this.rendered = tokenator.detokenate(parseDocumentElement(elem));
-  }
-
-  void write(PrintWriter printWriter) {
     printWriter.print(rendered);
   }
 
@@ -668,12 +684,11 @@
       XMLAttribute attribute) throws UnableToCompleteException {
 
     final String templateResourceName = attribute.getName().split(":")[0];
-    logger.log(TreeLogger.WARN, String.format(
-        "The %1$s mechanism is deprecated. Instead, declare the following "
-            + "%2$s:with element as a child of your %2$s:UiBinder element: "
-            + "<%2$s:with name='%3$s' type='%4$s.%5$s' />", BUNDLE_URI_SCHEME,
+    warn("The %1$s mechanism is deprecated. Instead, declare the following "
+        + "%2$s:with element as a child of your %2$s:UiBinder element: "
+        + "<%2$s:with name='%3$s' type='%4$s.%5$s' />", BUNDLE_URI_SCHEME,
         gwtPrefix, templateResourceName, bundleClass.getPackage().getName(),
-        bundleClass.getName()));
+        bundleClass.getName());
 
     // Try to find any bundle instance created with UiField.
     OwnerField field = getOwnerClass().getUiFieldForType(bundleClass);
@@ -755,10 +770,9 @@
       hasOldSchoolId = true;
       // If an id is specified on the element, use that.
       fieldName = elem.consumeAttribute("id");
-      logger.log(TreeLogger.WARN, String.format(
-          "Deprecated use of id=\"%1$s\" for field name. "
-              + "Please switch to gwt:field=\"%1$s\" instead. "
-              + "This will soon be a compile error!", fieldName));
+      warn("Deprecated use of id=\"%1$s\" for field name. "
+          + "Please switch to gwt:field=\"%1$s\" instead. "
+          + "This will soon be a compile error!", fieldName);
     }
     if (elem.hasAttribute(getUiFieldAttributeName())) {
       if (hasOldSchoolId) {
@@ -903,6 +917,45 @@
     return null;
   }
 
+  private Document parseXmlResource(final String resourcePath)
+      throws SAXParseException, UnableToCompleteException {
+    // Get the document builder. We need namespaces, and automatic expanding
+    // of entity references (the latter of which makes life somewhat easier
+    // for XMLElement).
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true);
+    factory.setExpandEntityReferences(true);
+    DocumentBuilder builder;
+    try {
+      builder = factory.newDocumentBuilder();
+    } catch (ParserConfigurationException e) {
+      throw new RuntimeException(e);
+    }
+
+    try {
+      ClassLoader classLoader = UiBinderGenerator.class.getClassLoader();
+      URL url = classLoader.getResource(resourcePath);
+      if (null == url) {
+        die("Unable to find resource: " + resourcePath);
+      }
+
+      InputStream stream = url.openStream();
+      InputSource input = new InputSource(stream);
+      input.setSystemId(url.toExternalForm());
+
+      builder.setEntityResolver(new GwtResourceEntityResolver());
+
+      return builder.parse(input);
+    } catch (SAXParseException e) {
+      // Let SAXParseExceptions through.
+      throw e;
+    } catch (SAXException e) {
+      throw new RuntimeException(e);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   private void registerParsers() {
     // TODO(rjrjr): Allow third-party parsers to register themselves
     // automagically, according to http://b/issue?id=1867118
@@ -943,9 +996,9 @@
   }
 
   /**
-   * Write statements that parsers created via calls to
-   * {@link #addInitStatement}. Such statements will assume that
-   * {@link #writeGwtFields} has already been called.
+   * Write statements that parsers created via calls to {@link #addStatement}.
+   * Such statements will assume that {@link #writeGwtFields} has already been
+   * called.
    */
   private void writeAddedStatements(IndentedWriter niceWriter) {
     for (String s : statements) {
@@ -985,7 +1038,7 @@
     writeHandlers(w);
     w.newline();
 
-    writeFieldSetters(w);
+    writeOwnerFieldSetters(w);
 
     writeCssInjectors(w);
 
@@ -1006,7 +1059,7 @@
   }
 
   private void writeCssInjectors(IndentedWriter w) {
-    for (CssResourceGetter css : bundleClass.getCssMethods()) {
+    for (ImplicitCssResource css : bundleClass.getCssMethods()) {
       w.write("ensureCssInjected(%s.%s());", bundleClass.getFieldName(),
           css.getName());
     }
@@ -1014,44 +1067,6 @@
   }
 
   /**
-   * Write the statements to fill in the fields of the UI owner.
-   */
-  private void writeFieldSetters(IndentedWriter niceWriter)
-      throws UnableToCompleteException {
-    for (OwnerField ownerField : getOwnerClass().getUiFields()) {
-      String fieldName = ownerField.getName();
-      FieldWriter fieldWriter = fieldManager.lookup(fieldName);
-
-      BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType().getRawType().getQualifiedSourceName());
-
-      if (bundleParser != null) {
-        // ownerField is a bundle resource.
-        maybeWriteFieldSetter(niceWriter, ownerField,
-            bundleParser.bundleClass(), bundleParser.bundleInstance());
-
-      } else if (fieldWriter != null) {
-        // ownerField is a widget.
-        JClassType type = fieldWriter.getType();
-        if (type != null) {
-          maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getType(),
-              fieldName);
-        } else {
-          // Must be a generated type
-          if (!ownerField.isProvided()) {
-            niceWriter.write("owner.%1$s = %1$s;", fieldName);
-          }
-        }
-
-      } else {
-        // ownerField was not found as bundle resource or widget, must die.
-        die("Template %s has no %s attribute for %s.%s#%s", templatePath,
-            getUiFieldAttributeName(), uiOwnerType.getPackage().getName(),
-            uiOwnerType.getName(), fieldName);
-      }
-    }
-  }
-
-  /**
    * Write declarations for variables or fields to hold elements declared with
    * gwt:field in the template. For those that have not had constructor
    * generation suppressed, emit GWT.create() calls instantiating them (or die
@@ -1096,7 +1111,7 @@
 
   /**
    * Write statements created by {@link #addInitStatement}. This code must be
-   * placed after all instnatiation code.
+   * placed after all instantiation code.
    */
   private void writeInitStatements(IndentedWriter niceWriter) {
     for (String s : initStatements) {
@@ -1104,6 +1119,44 @@
     }
   }
 
+  /**
+   * Write the statements to fill in the fields of the UI owner.
+   */
+  private void writeOwnerFieldSetters(IndentedWriter niceWriter)
+      throws UnableToCompleteException {
+    for (OwnerField ownerField : getOwnerClass().getUiFields()) {
+      String fieldName = ownerField.getName();
+      FieldWriter fieldWriter = fieldManager.lookup(fieldName);
+
+      BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType().getRawType().getQualifiedSourceName());
+
+      if (bundleParser != null) {
+        // ownerField is a bundle resource.
+        maybeWriteFieldSetter(niceWriter, ownerField,
+            bundleParser.bundleClass(), bundleParser.bundleInstance());
+
+      } else if (fieldWriter != null) {
+        // ownerField is a widget.
+        JClassType type = fieldWriter.getType();
+        if (type != null) {
+          maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getType(),
+              fieldName);
+        } else {
+          // Must be a generated type
+          if (!ownerField.isProvided()) {
+            niceWriter.write("owner.%1$s = %1$s;", fieldName);
+          }
+        }
+
+      } else {
+        // ownerField was not found as bundle resource or widget, must die.
+        die("Template %s has no %s attribute for %s.%s#%s", templatePath,
+            getUiFieldAttributeName(), uiOwnerType.getPackage().getName(),
+            uiOwnerType.getName(), fieldName);
+      }
+    }
+  }
+
   private void writePackage(IndentedWriter w) {
     String packageName = baseClass.getPackage().getName();
     if (packageName.length() > 0) {
@@ -1139,4 +1192,5 @@
     writeStaticMessagesInstance(w);
     writeStaticBundleInstances(w);
   }
+
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index 2f6d347..0ff9000 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -47,7 +47,7 @@
   public interface Interpreter<T> {
     /**
      * Given an XMLElement, return its filtered value.
-     * 
+     *
      * @throws UnableToCompleteException on error
      */
     T interpretElement(XMLElement elem) throws UnableToCompleteException;
@@ -115,7 +115,7 @@
   /**
    * Consumes the given attribute and returns its trimmed value, or null if it
    * was unset. The returned string is not escaped.
-   * 
+   *
    * @param name the attribute's full name (including prefix)
    * @return the attribute's value, or null
    */
@@ -128,7 +128,7 @@
   /**
    * Consumes the given attribute and returns its trimmed value, or the given
    * default value if it was unset. The returned string is not escaped.
-   * 
+   *
    * @param name the attribute's full name (including prefix)
    * @param defaultValue the value to return if the attribute was unset
    * @return the attribute's value, or defaultValue
@@ -143,7 +143,7 @@
 
   /**
    * Consumes the given attribute as a boolean value.
-   * 
+   *
    * @throws UnableToCompleteException
    */
   public boolean consumeBooleanAttribute(String attr)
@@ -177,9 +177,9 @@
    * Consumes and returns all child elements selected by the interpreter. Note
    * that text nodes are not elements, and so are not presented for
    * interpretation, and are not consumed.
-   * 
+   *
    * @param interpreter Should return true for any child that should be consumed
-   *          and returned.
+   *          and returned by the consumeChildElements call
    * @throws UnableToCompleteException
    */
   public Collection<XMLElement> consumeChildElements(
@@ -217,7 +217,7 @@
    * The odds are you want to use
    * {@link com.google.gwt.templates.parsers.HtmlInterpreter} for an HTML value,
    * or {@link com.google.gwt.templates.parsers.TextInterpreter} for text.
-   * 
+   *
    * @param interpreter Called for each element, expected to return a string
    *          replacement for it, or null if it should be left as is
    */
@@ -250,7 +250,7 @@
    * This call requires an interpreter to make sense of any special children.
    * The odds are you want to use
    * {@link com.google.gwt.templates.parsers.TextInterpreter}
-   * 
+   *
    * @throws UnableToCompleteException If any elements present are not consumed
    *           by the interpreter
    */
@@ -292,7 +292,7 @@
    */
   public String consumeOpeningTag() {
     String rtn = getOpeningTag();
-  
+
     for (int i = getAttributeCount() - 1; i >= 0; i--) {
       getAttribute(i).consumeValue();
     }
@@ -306,7 +306,7 @@
       throws UnableToCompleteException {
     String value = consumeAttribute(name);
     if ("".equals(value)) {
-      writer.die("In %s, missing required attribute name\"%s\"", this);
+      writer.die("In %s, missing required attribute name\"%s\"", this, name);
     }
     return value;
   }
@@ -319,8 +319,9 @@
     XMLElement ret = null;
     for (XMLElement child : consumeChildElements()) {
       if (ret != null) {
-        throw new RuntimeException(getLocalName()
-            + " may only contain a single child element.");
+        throw new RuntimeException(String.format(
+            "%s may only contain a single child element, but found"
+                + "%s and %s.", getLocalName(), ret, child));
       }
 
       ret = child;
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
index 6e4fe34..58e43cb 100644
--- a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
@@ -15,9 +15,9 @@
  */
 package com.google.gwt.uibinder.rebind.messages;
 
-import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.uibinder.rebind.IndentedWriter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLElement;
 
@@ -44,7 +44,7 @@
   private final String messagesNamespaceURI;
   private final String packageName;
   private final String messagesClassName;
-  private final TreeLogger logger;
+  private final MortalLogger logger;
   private final List<MessageWriter> messages = new ArrayList<MessageWriter>();
   private final String generatedFrom;
 
@@ -56,7 +56,7 @@
   private Map<XMLElement, Collection<AttributeMessage>> elemToAttributeMessages =
       new HashMap<XMLElement, Collection<AttributeMessage>>();
 
-  public MessagesWriter(String nameSpaceUri, TreeLogger logger, String generatedFrom,
+  public MessagesWriter(String nameSpaceUri, MortalLogger mortalLogger, String generatedFrom,
       String packageName, String uiBinderImplClassName) {
     this.messagesNamespaceURI = nameSpaceUri;
     this.generatedFrom = generatedFrom;
@@ -65,7 +65,7 @@
     // Localizable classes cannot have underscores in their names.
     this.messagesClassName = uiBinderImplClassName.replaceAll("_", "") + "GenMessages";
 
-    this.logger = logger;
+    this.logger = mortalLogger;
   }
 
   /**
@@ -108,10 +108,10 @@
     for (XMLElement child : messageChildren) {
       String attributeName = consumeMessageElementAttribute(NAME, child);
       if (attributeName.length() == 0) {
-        die(String.format("Missing name attribute in %s", child));
+        logger.die("Missing name attribute in %s", child);
       }
       if (!elem.hasAttribute(attributeName)) {
-        die(String.format("%s has no attribute matching %s", elem, child));
+        logger.die("%s has no attribute matching %s", elem, child);
       }
 
       String defaultMessage =
@@ -183,7 +183,7 @@
 
   /**
    * Confirm existence of an m:blah attribute on a non-message element, e.g.
-   * {@code <span m:ph="fnord"/>}
+   * {@code <span ui:ph="fnord"/>}
    */
   public boolean hasMessageAttribute(String attName, XMLElement elem) {
     String fullAttName = getMessagesPrefix() + ":" + attName;
@@ -268,9 +268,9 @@
     String fullAttName = getMessagesPrefix() + ":" + attName;
     if (elem.hasAttribute(fullAttName)) {
       String value = elem.consumeAttribute(fullAttName);
-      logger.log(TreeLogger.WARN, String.format(
+      logger.warn(
           "In %s, deprecated prefix \"%s:\" on \"%s\". Use \"%s\" instead.",
-          elem, getMessagesPrefix(), fullAttName, attName));
+          elem, getMessagesPrefix(), fullAttName, attName);
       return value;
     }
 
@@ -281,7 +281,7 @@
       XMLElement elem) throws UnableToCompleteException {
     String value = consumeMessageElementAttribute(attName, elem);
     if ("".equals(value)) {
-      die("%s does not have required attribute %s", elem, attName);
+      logger.die("%s does not have required attribute %s", elem, attName);
     }
     return value;
   }
@@ -302,25 +302,6 @@
     return declareMessage(newMessage);
   }
 
-  private void die(String message) throws UnableToCompleteException {
-    // TODO(rjrjr) copied from TemplateWriter. Move to common superclass or
-    // something
-    logger.log(TreeLogger.ERROR, message);
-    throw new UnableToCompleteException();
-  }
-
-  /**
-   * Post an error message and halt processing. This method always throws an
-   * {@link UnableToCompleteException}
-   */
-  private void die(String message, Object... params)
-      throws UnableToCompleteException {
-    // TODO(rjrjr) copied from TemplateWriter. Move to common superclass or
-    // something
-    logger.log(TreeLogger.ERROR, String.format(message, params));
-    throw new UnableToCompleteException();
-  }
-
   private void genInterfaceAnnotations(IndentedWriter pw) {
     pw.write("@GeneratedFrom(\"%s\")", generatedFrom);
     if (defaultLocale.length() > 0) {
@@ -339,7 +320,7 @@
           throws UnableToCompleteException {
         if (isAttributeMessage(child)) {
           if (child.hasChildNodes()) {
-            die(String.format("Illegal body for %s in %s.", child, elem));
+            logger.die("Illegal body for %s in %s.", child, elem);
           }
           return true;
         }
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/CssResourceGetter.java b/user/src/com/google/gwt/uibinder/rebind/model/CssResourceGetter.java
deleted file mode 100644
index ae0017a..0000000
--- a/user/src/com/google/gwt/uibinder/rebind/model/CssResourceGetter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2009 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.uibinder.rebind.model;
-
-import com.google.gwt.core.ext.typeinfo.JClassType;
-
-/**
- * Models a method returning a CssResource on a generated ClientBundle. At the
- * moment, they must implement a public interface and be tied to an external
- * .css file. That should improve in the next day or so.
- */
-public class CssResourceGetter {
-  private final String name;
-  private final String source;
-  private final JClassType extendedInterface;
-
-  public CssResourceGetter(String name, String source,
-      JClassType extendedInterface) {
-    this.name = name;
-    this.source = source;
-    this.extendedInterface = extendedInterface;
-  }
-
-  /**
-   * @return the public interface that this CssResource implements
-   */
-  public JClassType getExtendedInterface() {
-    return extendedInterface;
-  }
-
-  /**
-   * @return the name of this resource. This is both its method name in the
-   *         owning {@link ImplicitBundle} and its ui:field name
-   */
-  public String getName() {
-    return name;
-  }
-
-  /**
-   * @return the path to the associated .css file resource
-   */
-  public String getSource() {
-    return source;
-  }
-}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitBundle.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
similarity index 66%
rename from user/src/com/google/gwt/uibinder/rebind/model/ImplicitBundle.java
rename to user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
index abee4d3..3f17973 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitBundle.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
@@ -16,6 +16,7 @@
 package com.google.gwt.uibinder.rebind.model;
 
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -24,24 +25,29 @@
 /**
  * Models the ClientBundle to be generated from a ui.xml.
  */
-public class ImplicitBundle {
+public class ImplicitClientBundle {
 
-  private final Set<CssResourceGetter> cssMethods = new HashSet<CssResourceGetter>();
+  private final Set<ImplicitCssResource> cssMethods = new HashSet<ImplicitCssResource>();
   private final String packageName;
   private final String className;
   private final String fieldName;
+  private final String cssBaseName;
+  private final MortalLogger logger;
 
   /**
    * @param packageName Where the bundle should live
    * @param uiBinderImplClassName The name of the generated ui binder
    *          implementation that owns the bundle
    * @param fieldName The bundle's field name
+   * @param logger TODO
    */
-  public ImplicitBundle(String packageName, String uiBinderImplClassName,
-      String fieldName) {
+  public ImplicitClientBundle(String packageName, String uiBinderImplClassName,
+      String fieldName, MortalLogger logger) {
     this.packageName = packageName;
-    this.className = uiBinderImplClassName + "GenBundle";
+    this.className = uiBinderImplClassName + "_GenBundle";
+    this.cssBaseName = uiBinderImplClassName + "_GenCss";
     this.fieldName = fieldName;
+    this.logger = logger;
   }
 
   /**
@@ -51,11 +57,13 @@
    * @param source path to the .css file resource
    * @param extendedInterface the public interface implemented by this
    *          CssResource, or null
+   * @param body the inline css text
    * @return
    */
-  public CssResourceGetter createCssResource(String name, String source,
-      JClassType extendedInterface) {
-    CssResourceGetter css = new CssResourceGetter(name, source, extendedInterface);
+  public ImplicitCssResource createCssResource(String name, String source,
+      JClassType extendedInterface, String body) {
+    ImplicitCssResource css = new ImplicitCssResource(packageName, cssBaseName
+        + name, name, source, extendedInterface, body, logger);
     cssMethods.add(css);
     return css;
   }
@@ -64,7 +72,7 @@
     return className;
   }
 
-  public Set<CssResourceGetter> getCssMethods() {
+  public Set<ImplicitCssResource> getCssMethods() {
     return Collections.unmodifiableSet(cssMethods);
   }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
new file mode 100644
index 0000000..5742cec
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2009 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.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.resources.css.ExtractClassNamesVisitor;
+import com.google.gwt.resources.css.GenerateCssAst;
+import com.google.gwt.resources.css.ast.CssStylesheet;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.uibinder.rebind.MortalLogger;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Set;
+
+/**
+ * Models a method returning a CssResource on a generated ClientBundle. At the
+ * moment, they must be tied to an external .css file. That should improve in
+ * the next day or so.
+ */
+public class ImplicitCssResource {
+  private final String packageName;
+  private final String className;
+  private final String name;
+  private final String source;
+  private final JClassType extendedInterface;
+  private final String body;
+  private final MortalLogger logger;
+  private File generatedFile;
+
+  public ImplicitCssResource(String packageName, String className, String name,
+      String source, JClassType extendedInterface, String body,
+      MortalLogger logger) {
+    this.packageName = packageName;
+    this.className = className;
+    this.name = name;
+    this.extendedInterface = extendedInterface;
+    this.body = body;
+    this.logger = logger;
+
+    if (body.length() > 0) {
+      assert "".equals(source); // Enforced for real by the parser
+
+      /*
+       * We're going to write the inline body to a temporary File and register
+       * it with the CssResource world under the name in this.source, via
+       * ResourceGeneratorUtil.addNamedFile(). When CssResourceGenerator sees
+       * this name in an @Source annotation it will know to use the registered
+       * file rather than load a resource.
+       */
+      source = String.format("uibinder:%s.%s.css", packageName, className);
+    }
+    this.source = source;
+  }
+
+  /**
+   * @return the name of the CssResource interface
+   */
+  public String getClassName() {
+    return className;
+  }
+
+  /**
+   * @return the set of CSS classnames in the underlying .css files
+   *
+   * @throws UnableToCompleteException if the user has called for a .css file we
+   *           can't find.
+   */
+  public Set<String> getCssClassNames() throws UnableToCompleteException {
+    URL[] urls;
+
+    if (body.length() == 0) {
+      urls = getExternalCss();
+    } else {
+      try {
+        urls = new URL[] {getGeneratedFile().toURL()};
+      } catch (MalformedURLException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    CssStylesheet sheet = GenerateCssAst.exec(logger.getTreeLogger(), urls);
+    return ExtractClassNamesVisitor.exec(sheet);
+  }
+
+  /**
+   * @return the public interface that this CssResource implements
+   */
+  public JClassType getExtendedInterface() {
+    return extendedInterface;
+  }
+
+  /**
+   * @return the name of this resource. This is both its method name in the
+   *         owning {@link ImplicitClientBundle} and its ui:field name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * @return the package in which the generated CssResource interface should
+   *         reside
+   */
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /**
+   * @return the name of the .css file(s), separate by white space
+   */
+  public String getSource() {
+    return source;
+  }
+
+  private URL[] getExternalCss() throws UnableToCompleteException {
+    /*
+     * TODO(rjrjr,bobv) refactor ResourceGeneratorUtil.findResources so we can
+     * find them the same way ClientBundle does. For now, just look relative to
+     * this package
+     */
+
+    ClassLoader classLoader = ImplicitCssResource.class.getClassLoader();
+    String path = packageName.replace(".", "/");
+
+    String[] sources = source.split(" ");
+    URL[] urls = new URL[sources.length];
+    int i = 0;
+
+    for (String s : sources) {
+      String resourcePath = path + '/' + s;
+      URL found = classLoader.getResource(resourcePath);
+      if (null == found) {
+        logger.die("Unable to find resource: " + resourcePath);
+      }
+      urls[i++] = found;
+    }
+    return urls;
+  }
+
+  private File getGeneratedFile() {
+    if (generatedFile == null) {
+      try {
+        File f = File.createTempFile(String.format("uiBinder_%s_%s",
+            packageName, className), ".css");
+
+        BufferedWriter out = new BufferedWriter(new FileWriter(f));
+        out.write(body);
+        out.close();
+        generatedFile = f;
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      ResourceGeneratorUtil.addNamedFile(getSource(), generatedFile);
+    }
+    return generatedFile;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
index 5250c8c..c7ee4f0 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
@@ -22,6 +22,7 @@
 import com.google.gwt.uibinder.client.UiFactory;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -61,12 +62,16 @@
    */
   private final List<JMethod> uiHandlers = new ArrayList<JMethod>();
 
+  private final MortalLogger logger;
+
   /**
    * Constructor.
    *
    * @param ownerType the type of the owner class
+   * @param logger
    */
-  public OwnerClass(JClassType ownerType) throws UnableToCompleteException {
+  public OwnerClass(JClassType ownerType, MortalLogger logger) throws UnableToCompleteException {
+    this.logger = logger;
     findUiFields(ownerType);
     findUiFactories(ownerType);
     findUiHandlers(ownerType);
@@ -105,7 +110,7 @@
    * @return the field descriptor
    * @deprecated This will die with {@link com.google.gwt.uibinder.parsers.BundleAttributeParser}
    */
-  @Deprecated 
+  @Deprecated
   public OwnerField getUiFieldForType(JClassType type) {
     return uiFieldTypes.get(type);
   }
@@ -127,9 +132,9 @@
   /**
    * Scans the owner class to find all methods annotated with @UiFactory, and
    * puts them in {@link #uiFactories}.
-   * 
+   *
    * @param ownerType the type of the owner class
-   * @throws UnableToCompleteException 
+   * @throws UnableToCompleteException
    */
   private void findUiFactories(JClassType ownerType)
       throws UnableToCompleteException {
@@ -139,18 +144,14 @@
         JClassType factoryType = method.getReturnType().isClassOrInterface();
 
         if (factoryType == null) {
-          // TODO(rdamazio): proper logging
-          System.out.println("Factory return type is not a class in method "
+          logger.die("Factory return type is not a class in method "
               + method.getName());
-          throw new UnableToCompleteException();
         }
 
         if (uiFactories.containsKey(factoryType)) {
-          // TODO(rdamazio): proper logging
-          System.out.println("Duplicate factory in class "
+          logger.die("Duplicate factory in class "
               + method.getEnclosingType().getName() + " for type "
               + factoryType.getName());
-          throw new UnableToCompleteException();
         }
 
         uiFactories.put(factoryType, method);
@@ -167,7 +168,7 @@
   /**
    * Scans the owner class to find all fields annotated with @UiField, and puts
    * them in {@link #uiFields} and {@link #uiFieldTypes}.
-   * 
+   *
    * @param ownerType the type of the owner class
    */
   private void findUiFields(JClassType ownerType)
@@ -178,13 +179,11 @@
         JClassType ownerFieldType = field.getType().isClassOrInterface();
 
         if (ownerFieldType == null) {
-          // TODO(rdamazio): proper logging
-          System.out.println("Field type is not a class in field "
+          logger.die("Field type is not a class in field "
               + field.getName());
-          throw new UnableToCompleteException();
         }
 
-        OwnerField ownerField = new OwnerField(field);
+        OwnerField ownerField = new OwnerField(field, logger);
         String ownerFieldName = field.getName();
 
         uiFields.put(ownerFieldName, ownerField);
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
index e319827..d257000 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 /**
  * Descriptor for a field of the owner class.
@@ -38,29 +39,26 @@
    * Constructor.
    *
    * @param field the field of the owner class
+   * @param logger
    */
-  public OwnerField(JField field) throws UnableToCompleteException {
+  public OwnerField(JField field, MortalLogger logger) throws UnableToCompleteException {
     this.name = field.getName();
 
     // Get the field type and ensure it's a class or interface
     JClassType fieldClassType = field.getType().isClassOrInterface();
 
     if (fieldClassType == null) {
-      // TODO(rdamazio): proper logging
-      System.out.println("Type for field " + name + " is not a class: "
+      logger.die("Type for field " + name + " is not a class: "
           + field.getType().getSimpleSourceName());
-      throw new UnableToCompleteException();
     }
 
-    this.fieldType = OwnerFieldClass.getFieldClass(fieldClassType);
+    this.fieldType = OwnerFieldClass.getFieldClass(fieldClassType, logger);
 
     // Get the UiField annotation and process it
     UiField annotation = field.getAnnotation(UiField.class);
 
     if (annotation == null) {
-      // TODO(rdamazio): proper logging
-      System.out.println("Field " + name + " is not annotated with @UiField");
-      throw new UnableToCompleteException();
+      logger.die("Field " + name + " is not annotated with @UiField");
     }
 
     isProvided = annotation.provided();
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
index 772b959..9622998 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
@@ -23,6 +23,7 @@
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -32,31 +33,31 @@
 import java.util.Set;
 
 /**
- * Descriptor for a class which can be used as a @UiField.
- * This is usually a widget, but can also be a resource (such as Messages or
- * an ImageBundle). Also notice that the existence of an OwnerFieldClass doesn't
- * mean the class is actually present as a field in the owner.
+ * Descriptor for a class which can be used as a @UiField. This is usually a
+ * widget, but can also be a resource (such as Messages or an ImageBundle). Also
+ * notice that the existence of an OwnerFieldClass doesn't mean the class is
+ * actually present as a field in the owner.
  */
 public class OwnerFieldClass {
 
   /**
-   * Global map of field classes.
-   * This serves as a cache so each class is only processed once.
+   * Global map of field classes. This serves as a cache so each class is only
+   * processed once.
    */
-  private static final Map<JClassType, OwnerFieldClass> FIELD_CLASSES =
-      new HashMap<JClassType, OwnerFieldClass>();
+  private static final Map<JClassType, OwnerFieldClass> FIELD_CLASSES = new HashMap<JClassType, OwnerFieldClass>();
 
   /**
    * Gets or creates the descriptor for the given field class.
    *
    * @param forType the field type to get a descriptor for
+   * @param logger TODO
    * @return the descriptor
    */
-  public static OwnerFieldClass getFieldClass(JClassType forType)
-      throws UnableToCompleteException {
+  public static OwnerFieldClass getFieldClass(JClassType forType,
+      MortalLogger logger) throws UnableToCompleteException {
     OwnerFieldClass clazz = FIELD_CLASSES.get(forType);
     if (clazz == null) {
-      clazz = new OwnerFieldClass(forType);
+      clazz = new OwnerFieldClass(forType, logger);
       FIELD_CLASSES.put(forType, clazz);
     }
     return clazz;
@@ -66,16 +67,19 @@
   private final Map<String, JMethod> setters = new HashMap<String, JMethod>();
   private Set<String> ambiguousSetters;
   private JConstructor uiConstructor;
+  private final MortalLogger logger;
 
   /**
-   * Default constructor.
-   * This is package-visible for testing only.
+   * Default constructor. This is package-visible for testing only.
    *
    * @param forType the type of the field class
+   * @param logger
    * @throws UnableToCompleteException if the class is not valid
    */
-  OwnerFieldClass(JClassType forType) throws UnableToCompleteException {
+  OwnerFieldClass(JClassType forType, MortalLogger logger)
+      throws UnableToCompleteException {
     this.rawType = forType;
+    this.logger = logger;
 
     findUiConstructor(forType);
     findSetters(forType);
@@ -101,10 +105,8 @@
     // when CheckBox#setChecked will go away and CheckBox#setValue must be used
 
     if (ambiguousSetters != null && ambiguousSetters.contains(propertyName)) {
-      // TODO(rdamazio): proper logging
-      System.out.println("Ambiguous setter requested: " + rawType.getName()
-          + "." + propertyName);
-      throw new UnableToCompleteException();
+      logger.die("Ambiguous setter requested: " + rawType.getName() + "."
+          + propertyName);
     }
 
     return setters.get(propertyName);
@@ -120,9 +122,8 @@
 
   /**
    * Given a collection of setters for the same property, picks which one to
-   * use.
-   * Not having a proper setter is not an error unless of course the user tries
-   * to use it.
+   * use. Not having a proper setter is not an error unless of course the user
+   * tries to use it.
    *
    * @param propertySetters the collection of setters
    * @return the setter to use, or null if none is good enough
@@ -167,8 +168,7 @@
    * @param fieldType the leaf type to look at
    * @return a multimap of property name to the setter methods
    */
-  private Map<String, Collection<JMethod>> findAllSetters(
-      JClassType fieldType) {
+  private Map<String, Collection<JMethod>> findAllSetters(JClassType fieldType) {
     Map<String, Collection<JMethod>> allSetters;
 
     // First, get all setters from the parent class, recursively.
@@ -246,10 +246,8 @@
     for (JConstructor ctor : fieldType.getConstructors()) {
       if (ctor.getAnnotation(UiConstructor.class) != null) {
         if (uiConstructor != null) {
-          // TODO(rdamazio): proper logging
-          System.out.println(fieldType.getName()
+          logger.die(fieldType.getName()
               + " has more than one constructor annotated with @UiConstructor");
-          throw new UnableToCompleteException();
         }
         uiConstructor = ctor;
       }
@@ -257,9 +255,8 @@
   }
 
   /**
-   * Checks whether the given method qualifies as a setter.
-   * This looks at the method qualifiers, name and return type, but not at the
-   * parameter types.
+   * Checks whether the given method qualifies as a setter. This looks at the
+   * method qualifiers, name and return type, but not at the parameter types.
    *
    * @param method the method to look at
    * @return whether it's a setter
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
index 9bd75be..c712178 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
@@ -2,3 +2,11 @@
   border:solid;
   background-color:white;
 }
+
+/*
+ * Demonstrates that the ui.xml has access to styles that 
+ * do not back any declared CssResource api
+ */
+.privateColor {
+  color: SteelBlue;
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
index 1fde1b3..edb568f 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -25,6 +25,7 @@
 import com.google.gwt.dom.client.StyleInjector;
 import com.google.gwt.dom.client.TableElement;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Shared;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiFactory;
 import com.google.gwt.uibinder.client.UiField;
@@ -50,6 +51,7 @@
    * This CssResource is a requirement of the WidgetBasedUi, to be provided by
    * its ui.xml template.
    */
+  @Shared
   public interface Style extends CssResource {
     String menuBar();
   }
@@ -108,6 +110,9 @@
   @UiField OListElement widgetCrazyOrderedList;
   @UiField DListElement widgetCrazyDefinitionList;
   @UiField HTMLPanel customTagHtmlPanel;
+  @UiField ParagraphElement privateStyleParagraph;
+  @UiField ParagraphElement reallyPrivateStyleParagraph;
+  @UiField SpanElement totallyPrivateStyleSpan;
 
   public WidgetBasedUi() {
     this.bundledLabel = new Label();
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
index 0d2db28..33ba429 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -69,8 +69,30 @@
 <!-- 
   Tests creating a CssResource from an external file.
  -->
-<!--<ui:style field='myStyle' source='WidgetBasedUi.css' type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>-->
-<ui:style field='myStyle' source='WidgetBasedUi.css' type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>
+<ui:style field='myStyle' source='WidgetBasedUi.css' 
+    type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>
+
+<ui:style field='myOtherStyle' type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'>
+  /* because we extend WidgetBasedUi.Style and it's tagged @Shared, we can refine the menubar 
+     style defined in other files */
+  
+  .menuBar.psychedelic {
+    background-color: Yellow;
+  }
+
+  /* Visible only to this template  */
+  .privateColor {
+    color: Purple;
+  }
+</ui:style>
+
+<ui:style field='myTotallyPrivateStyle'>
+  /* An inline style tied to no public type */
+  
+  .superPrivateColor {
+    background-color: Gold;
+  }
+</ui:style>
 
 <gwt:DockPanel ui:field="root" width="100%">
   <gwt:Dock direction='NORTH'>
@@ -150,7 +172,7 @@
                 Piggledy
               </ui:msg>
               </div>
-            <gwt:MenuBar vertical="true" styleName="{myStyle.menuBar}">
+            <gwt:MenuBar vertical="true" styleName="{myStyle.menuBar} {myOtherStyle.psychedelic}">
               <gwt:MenuItem>delta</gwt:MenuItem>
               <gwt:MenuItem>echo</gwt:MenuItem>
               <gwt:MenuItem>foxtrot</gwt:MenuItem>
@@ -256,13 +278,23 @@
  
       <h2>Stylish</h2>
       <p>
-       Templates work with what is now called ImmutableResourceBundle, but
-       which you will soon come to know and love as ClientBundle. For example,
-       this label gets its text from somplace tricky and its style from a resource:</p>
+       Templates work with ClientBundle. For example,
+       this label gets its text from somplace tricky and its style from a ClientBundle
+       defined outside of this UI:</p>
       <gwt:Label ui:field='bundledLabel' text="{values.helloText}" styleName="{external.style.prettyText}"/>
+      <!-- Note use of id rather than ui:field, to test that ids work on dom elements -->
       <p class="{external.style.prettyText}" id='prettyPara'>
-        This stylish paragraph also gets its good looks from a
-        resource.
+        This stylish paragraph also gets its good looks from the
+        external ClientBundle.
+      </p>
+      <p ui:field='privateStyleParagraph' class='{myStyle.privateColor}'>
+        This one is has a private style, defined out in WidgetBaseUi.css and used only by this ui.xml file.
+      </p>
+      <p ui:field='reallyPrivateStyleParagraph' class='{myOtherStyle.privateColor}'>
+        And this style is defined right here inside the ui.xml file. 
+        <span ui:field='totallyPrivateStyleSpan' class='{myTotallyPrivateStyle.superPrivateColor}'>
+          (This one too, but even more so.)
+        </span>
       </p>
       <h2>Evolving</h2>
       <p>
diff --git a/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
index 4281dde..58e15d3 100644
--- a/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
@@ -101,6 +101,7 @@
    * 
    * @param widget the widget to be added
    * @param direction the widget's direction in the dock
+   * @param size the child widget's size
    * 
    * @throws IllegalArgumentException when adding to the {@link #CENTER} and
    *           there is already a different widget there
@@ -114,7 +115,7 @@
    */
   public Element getContainerElementFor(Widget widget) {
     assertIsChild(widget);
-    return ((LayoutData)widget.getLayoutData()).layer.getContainerElement();
+    return ((LayoutData) widget.getLayoutData()).layer.getContainerElement();
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
new file mode 100644
index 0000000..46a43e8
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2009 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.dom.client.Document;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Event;
+
+/**
+ * A panel that adds user-positioned splitters between each of its child
+ * widgets.
+ * 
+ * <p>
+ * This panel is used in the same way as {@link DockLayoutPanel}, except that
+ * its children's sizes are always specified in {@link Unit#PX} units, and each
+ * pair of child widgets has a splitter between them that the user can drag.
+ * </p>
+ * 
+ * <p>
+ * This widget will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ * 
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ * 
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.SplitLayoutPanelExample}
+ * </p>
+ * 
+ * TODO(jgw):
+ * - RTL Support.
+ * - implement insert().
+ * - Come up with a decent way to specify splitter style and size.
+ */
+public class SplitLayoutPanel extends DockLayoutPanel {
+
+  private class HSplitter extends Splitter {
+    public HSplitter(Widget target, boolean reverse) {
+      super(target, reverse);
+      getElement().getStyle().setPropertyPx("width", 4);
+      setStyleName("LayoutPanel-HDragger");
+    }
+
+    @Override
+    protected int getAbsolutePosition() {
+      return getAbsoluteLeft();
+    }
+
+    @Override
+    protected int getEventPosition(Event event) {
+      return event.getClientX();
+    }
+
+    @Override
+    protected int getTargetPosition() {
+      return target.getAbsoluteLeft();
+    }
+
+    @Override
+    protected int getTargetSize() {
+      return target.getOffsetWidth();
+    }
+  }
+
+  private abstract class Splitter extends Widget {
+    protected final Widget target;
+
+    private int offset;
+    private boolean mouseDown;
+    private Command layoutCommand;
+
+    private final boolean reverse;
+    private int minSize;
+
+    public Splitter(Widget target, boolean reverse) {
+      this.target = target;
+      this.reverse = reverse;
+
+      setElement(Document.get().createDivElement());
+      sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONMOUSEMOVE
+          | Event.ONDBLCLICK);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+      switch (event.getTypeInt()) {
+        case Event.ONMOUSEDOWN:
+          mouseDown = true;
+          offset = getEventPosition(event) - getAbsolutePosition();
+          Event.setCapture(getElement());
+          event.preventDefault();
+          break;
+
+        case Event.ONMOUSEUP:
+          mouseDown = false;
+          Event.releaseCapture(getElement());
+          event.preventDefault();
+          break;
+
+        case Event.ONMOUSEMOVE:
+          if (mouseDown) {
+            int size;
+            if (reverse) {
+              size = getTargetPosition() + getTargetSize()
+                  - getEventPosition(event) - offset;
+            } else {
+              size = getEventPosition(event) - getTargetPosition() - offset;
+            }
+
+            setAssociatedWidgetSize(size);
+            event.preventDefault();
+          }
+          break;
+      }
+    }
+
+    public void setMinSize(int minSize) {
+      this.minSize = minSize;
+      LayoutData layout = (LayoutData) target.getLayoutData();
+
+      // Try resetting the associated widget's size, which will enforce the new
+      // minSize value.
+      setAssociatedWidgetSize((int) layout.size);
+    }
+
+    protected abstract int getAbsolutePosition();
+
+    protected abstract int getEventPosition(Event event);
+
+    protected abstract int getTargetPosition();
+
+    protected abstract int getTargetSize();
+
+    private void setAssociatedWidgetSize(int size) {
+      if (size < minSize) {
+        size = minSize;
+      }
+
+      LayoutData layout = (LayoutData) target.getLayoutData();
+      if (size == layout.size) {
+        return;
+      }
+
+      layout.size = size;
+
+      // Defer actually updating the layout, so that if we receive many
+      // mouse events before layout/paint occurs, we'll only update once.
+      if (layoutCommand == null) {
+        layoutCommand = new Command() {
+          public void execute() {
+            layoutCommand = null;
+            layout();
+          }
+        };
+        DeferredCommand.addCommand(layoutCommand);
+      }
+    }
+  }
+
+  private class VSplitter extends Splitter {
+    public VSplitter(Widget target, boolean reverse) {
+      super(target, reverse);
+      getElement().getStyle().setPropertyPx("height", 4);
+      setStyleName("LayoutPanel-VDragger");
+    }
+
+    @Override
+    protected int getAbsolutePosition() {
+      return getAbsoluteTop();
+    }
+
+    @Override
+    protected int getEventPosition(Event event) {
+      return event.getClientY();
+    }
+
+    @Override
+    protected int getTargetPosition() {
+      return target.getAbsoluteTop();
+    }
+
+    @Override
+    protected int getTargetSize() {
+      return target.getOffsetHeight();
+    }
+  }
+
+  private static final int SPLITTER_SIZE = 4;
+
+  public SplitLayoutPanel() {
+    super(Unit.PX);
+  }
+
+  @Override
+  public void add(Widget child, Direction direction, double size) {
+    super.add(child, direction, size);
+    if (direction != Direction.CENTER) {
+      addSplitter();
+    }
+  }
+
+  @Override
+  public boolean remove(Widget child) {
+    assert !(child instanceof Splitter) : "Splitters may not be directly removed";
+
+    if (super.remove(child)) {
+      // Remove the associated splitter, if any.
+      int idx = getWidgetIndex(child);
+      if (idx < getWidgetCount() - 1) {
+        remove(idx + 1);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Sets the minimum allowable size for the given widget.
+   * 
+   * <p>
+   * Its assocated splitter cannot be dragged to a position that would make it
+   * smaller than this size. This method has no effect for the
+   * {@link Direction#CENTER} widget.
+   * </p>
+   * 
+   * @param child the child whose minimum size will be set
+   * @param minSize the minimum size for this widget
+   */
+  public void setWidgetMinSize(Widget child, int minSize) {
+    Splitter splitter = getAssociatedSplitter(child);
+    splitter.setMinSize(minSize);
+  }
+
+  private void addSplitter() {
+    assert getChildren().size() > 0 : "Can't add a splitter before any children";
+    assert getCenter() == null : "Can't add a splitter after the CENTER widget";
+
+    Widget lastChild = getChildren().get(getChildren().size() - 1);
+    LayoutData lastChildLayout = (LayoutData) lastChild.getLayoutData();
+    Splitter splitter;
+    switch (lastChildLayout.direction) {
+      case WEST:
+        splitter = new HSplitter(lastChild, false);
+        break;
+      case EAST:
+        splitter = new HSplitter(lastChild, true);
+        break;
+      case NORTH:
+        splitter = new VSplitter(lastChild, false);
+        break;
+      case SOUTH:
+        splitter = new VSplitter(lastChild, true);
+        break;
+      default:
+        assert false : "Unexpected direction";
+        return;
+    }
+
+    super.add(splitter, lastChildLayout.direction, SPLITTER_SIZE);
+  }
+
+  private Splitter getAssociatedSplitter(Widget child) {
+    // If a widget has a next sibling, it must be a splitter, because the only
+    // widget that *isn't* followed by a splitter must be the CENTER, which has
+    // no associated splitter.
+    int idx = getWidgetIndex(child);
+    if (idx < getWidgetCount() - 2) {
+      Widget splitter = getWidget(idx + 1);
+      assert splitter instanceof Splitter : "Expected child widget to be splitter";
+      return (Splitter) splitter;
+    }
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java
new file mode 100644
index 0000000..a280d32
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2009 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.dom.client.Style.Unit;
+import com.google.gwt.layout.client.Layout.AnimationCallback;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.user.client.Event;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A panel that stacks its children vertically, displaying only one at a time,
+ * with a header for each child which the user can click to display.
+ * 
+ * <p>
+ * This widget will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ * 
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ * 
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.StackLayoutPanelExample}
+ * </p>
+ * 
+ * TODO(jgw):
+ * - implement insert().
+ * - add() methods with default widgets for headers.
+ * - some way to get the header widget associated with a child.
+ * - make animation configurable (with {@link HasAnimation}).
+ * - default style.
+ */
+public class StackLayoutPanel extends Composite implements HasWidgets,
+    RequiresLayout, RequiresResize, ProvidesResize {
+
+  private class ClickWrapper extends Composite {
+    private Widget target;
+
+    public ClickWrapper(Widget target, Widget wrappee) {
+      this.target = target;
+      initWidget(wrappee);
+      sinkEvents(Event.ONCLICK);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+      if (event.getTypeInt() == Event.ONCLICK) {
+        showWidget(target);
+      }
+    }
+  }
+
+  private static class LayoutData {
+    public double headerSize;
+    public Widget header;
+    public Widget widget;
+    public Layer widgetLayer;
+    public Layer headerLayer;
+
+    public LayoutData(Widget widget, Widget header, double headerSize,
+        Layer widgetLayer, Layer headerLayer) {
+      this.widget = widget;
+      this.header = header;
+      this.headerSize = headerSize;
+      this.widgetLayer = widgetLayer;
+      this.headerLayer = headerLayer;
+    }
+  }
+
+  private static final int ANIMATION_TIME = 250;
+
+  private LayoutPanel layoutPanel;
+  private Unit unit;
+  private ArrayList<LayoutData> layoutData = new ArrayList<LayoutData>();
+  private Widget visibleWidget;
+
+  /**
+   * Creates an empty stack panel.
+   * 
+   * @param unit the unit to be used for layout
+   */
+  public StackLayoutPanel(Unit unit) {
+    this.unit = unit;
+    initWidget(layoutPanel = new LayoutPanel());
+  }
+
+  public void add(Widget w) {
+    assert false : "Single-argument add() is not supported for this widget";
+  }
+
+  /**
+   * Adds a child widget to this stack, along with a widget representing the
+   * stack header.
+   * 
+   * @param widget the child widget to be added
+   * @param header the header widget
+   * @param headerSize the size of the header widget
+   */
+  public void add(Widget widget, Widget header, double headerSize) {
+    ClickWrapper wrapper = new ClickWrapper(widget, header);
+    layoutPanel.add(wrapper);
+    layoutPanel.add(widget);
+
+    Layer headerLayer = layoutPanel.getLayer(wrapper);
+    headerLayer.setLeftRight(0, Unit.PX, 0, Unit.PX);
+
+    Layer widgetLayer = layoutPanel.getLayer(widget);
+    widgetLayer.setLeftRight(0, Unit.PX, 0, Unit.PX);
+
+    LayoutData data = new LayoutData(widget, wrapper, headerSize, widgetLayer,
+        headerLayer);
+    layoutData.add(data);
+
+    if (visibleWidget == null) {
+      visibleWidget = widget;
+    }
+  }
+
+  public void clear() {
+    layoutPanel.clear();
+    visibleWidget = null;
+  }
+
+  public Iterator<Widget> iterator() {
+    return new Iterator<Widget>() {
+      int i = 0, last = -1;
+
+      public boolean hasNext() {
+        return i < layoutData.size();
+      }
+
+      public Widget next() {
+        if (!hasNext()) {
+          throw new NoSuchElementException();
+        }
+        return layoutData.get(last = i++).widget;
+      }
+
+      public void remove() {
+        if (last < 0) {
+          throw new IllegalStateException();
+        }
+
+        StackLayoutPanel.this.remove(layoutData.get(last).widget);
+        i = last;
+        last = -1;
+      }
+    };
+  }
+
+  public void layout() {
+    layout(0);
+  }
+
+  public void layout(int duration) {
+    layout(duration, null);
+  }
+
+  public void layout(int duration, AnimationCallback callback) {
+    int top = 0, bottom = 0;
+    int i = 0, visibleIndex = -1;
+    for (; i < layoutData.size(); ++i) {
+      LayoutData data = layoutData.get(i);
+      data.headerLayer.setTopHeight(top, unit, data.headerSize, unit);
+
+      top += data.headerSize;
+
+      data.widgetLayer.setTopHeight(top, unit, 0, unit);
+
+      if (data.widget == visibleWidget) {
+        visibleIndex = i;
+        break;
+      }
+    }
+
+    assert visibleIndex != -1;
+
+    for (int j = layoutData.size() - 1; j > i; --j) {
+      LayoutData data = layoutData.get(j);
+      data.headerLayer.setBottomHeight(bottom, unit, data.headerSize, unit);
+      data.widgetLayer.setBottomHeight(bottom, unit, 0, unit);
+      bottom += data.headerSize;
+    }
+
+    LayoutData data = layoutData.get(visibleIndex);
+    data.widgetLayer.setTopBottom(top, unit, bottom, unit);
+
+    layoutPanel.layout(duration, callback);
+  }
+
+  public void onResize() {
+    layoutPanel.onResize();
+  }
+
+  public boolean remove(Widget child) {
+    if (child.getParent() != this) {
+      return false;
+    }
+
+    LayoutData data = (LayoutData) child.getLayoutData();
+    layoutPanel.remove(data.header);
+    layoutPanel.remove(child);
+    return true;
+  }
+
+  /**
+   * Shows the specified widget.
+   * 
+   * @param widget the child widget to be shown.
+   */
+  public void showWidget(Widget widget) {
+    visibleWidget = widget;
+    layout(ANIMATION_TIME);
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
index 82a842b..e2ba40c 100644
--- a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
@@ -24,6 +24,7 @@
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.DisclosurePanel;
 import com.google.gwt.user.client.ui.DockPanel;
@@ -235,6 +236,19 @@
         domUi.root.getClassName());
   }
 
+  interface Bundle extends ClientBundle {
+    @Source("WidgetBasedUi.css")
+    public WidgetBasedUi.Style style();
+  }
+
+  public void testNoOverrideInheritedSharedCssClasses() {
+    Bundle bundle = GWT.create(Bundle.class);
+    WidgetBasedUi ui = GWT.create(WidgetBasedUi.class);
+    String publicStyle = bundle.style().menuBar();
+    String privateStyle = ui.myStyle.menuBar();
+    assertEquals(publicStyle, privateStyle);
+  }
+
   public void suppressedForIEfail_testNonXmlEntities() {
     // This fragment includes both translated and non-translated strings
     ParagraphElement mainParagraph = widgetUi.main;
@@ -251,6 +265,22 @@
     assertTrue(((HTML) north).getHTML().contains("Title area"));
   }
 
+  public void testPrivateStyleFromExternalCss() {
+    ParagraphElement p = widgetUi.privateStyleParagraph;
+    assertTrue("Some kind of class should be set", p.getClassName().length() > 0);
+  }
+
+  public void testPrivateStylesFromInlineCss() {
+    ParagraphElement p = widgetUi.reallyPrivateStyleParagraph;
+    assertTrue("Some kind of class should be set",
+        p.getClassName().length() > 0);
+    assertFalse("Should be a different style than privateStyleParagraph's",
+        widgetUi.privateStyleParagraph.getClassName().equals(p.getClassName()));
+
+    assertTrue("Some kind of class should be set",
+        widgetUi.totallyPrivateStyleSpan.getClassName().length() > 0);
+  }
+
   @DoNotRunWith(Platform.Htmlunit)
   public void testRadioButton() {
     RadioButton able = widgetUi.myRadioAble;
diff --git a/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java b/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
index 9409b7d..94fa845 100644
--- a/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
@@ -124,7 +124,7 @@
    */
   public void testCustomRootTagAsTable() {
     HTMLPanel hp = new HTMLPanel("table",
-        "<tr><td>Hello <span id='labelHere'></span></td></tr></table>");
+        "<tr><td>Hello <span id='labelHere'></span></td></tr>");
     InlineLabel label = new InlineLabel("World");
     hp.addAndReplaceElement(label, "labelHere");