All the hard parts of
http://code.google.com/p/google-web-toolkit/issues/detail?id=3984 are done.

We will now generate a CssResource interface for you, though you still have to
bring in the actual css from a separate .css file. The next step is to allow css
inside the ui.xml file, but that'll be easy now that all the groundwork is
done.

Note that changes were required to CssResourceGenerator to allow it to handle
generated CssResource interfaces.

Also refactored instantiating PrintWriters and logging--no more redundant copies
of die() and warn(), and no more invisible errors logged to stderr instead of
the TreeLogger.

This cleanup also lead me to move XML knowledge out of UiBinderGenerator and
into UiBinderWriter, but that was a completely mechanical
refactor--parseXmlResource has not changed.

reviewed by: spoon


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6102 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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..a6fcec7 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -21,8 +21,8 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.resources.client.CssResource;
 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,10 +38,11 @@
   private final TypeOracle oracle;
   private final MessagesWriter messagesWriter;
   private final FieldManager fieldManager;
-  private final ImplicitBundle bundleClass;
+  private final ImplicitClientBundle bundleClass;
 
   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;
@@ -68,7 +69,8 @@
     JClassType publicType = consumeTypeAttribute(elem);
     JClassType cssResourceType = oracle.findType(CssResource.class.getCanonicalName());
     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;
@@ -134,9 +136,10 @@
     String name = elem.consumeAttribute("field", "style");
     JClassType publicType = consumeCssResourceType(elem);
 
-    CssResourceGetter cssMethod = bundleClass.createCssResource(name, source,
+    ImplicitCssResource cssMethod = bundleClass.createCssResource(name, source,
         publicType);
-    FieldWriter field = fieldManager.registerField(publicType,
+    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/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 71%
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..3c1457e 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.cssBaseName = uiBinderImplClassName + "GenCss";
     this.fieldName = fieldName;
+    this.logger = logger;
   }
 
   /**
@@ -53,9 +59,10 @@
    *          CssResource, or null
    * @return
    */
-  public CssResourceGetter createCssResource(String name, String source,
+  public ImplicitCssResource createCssResource(String name, String source,
       JClassType extendedInterface) {
-    CssResourceGetter css = new CssResourceGetter(name, source, extendedInterface);
+    ImplicitCssResource css = new ImplicitCssResource(packageName, cssBaseName
+        + name, name, source, extendedInterface, logger);
     cssMethods.add(css);
     return css;
   }
@@ -64,7 +71,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..76284da
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
@@ -0,0 +1,119 @@
+/*
+ * 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.uibinder.rebind.MortalLogger;
+
+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 MortalLogger logger;
+
+  public ImplicitCssResource(String packageName, String className, String name,
+      String source, JClassType extendedInterface, MortalLogger logger) {
+    this.packageName = packageName;
+    this.className = className;
+    this.name = name;
+    this.source = source;
+    this.extendedInterface = extendedInterface;
+    this.logger = logger;
+  }
+
+  /**
+   * @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 {
+    /*
+     * 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;
+    }
+
+    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 user declared names of the associated .css files
+   */
+  public String getSource() {
+    return source;
+  }
+}
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..3d0f78d 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
+ */
+.privateStyle {
+  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..bd47e88 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,7 @@
   @UiField OListElement widgetCrazyOrderedList;
   @UiField DListElement widgetCrazyDefinitionList;
   @UiField HTMLPanel customTagHtmlPanel;
+  @UiField ParagraphElement privateStyleParagraph;
 
   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..288690d 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,7 +69,6 @@
 <!-- 
   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'/>
 
 <gwt:DockPanel ui:field="root" width="100%">
@@ -256,13 +255,17 @@
  
       <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.privateStyle}'>
+        This one is has a private style, visible only inside the ui.xml file.
       </p>
       <h2>Evolving</h2>
       <p>
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..5172b0c 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,11 @@
     assertTrue(((HTML) north).getHTML().contains("Title area"));
   }
 
+  public void testPrivateStyle() {
+    ParagraphElement p = widgetUi.privateStyleParagraph;
+    assertTrue("Some kind of class should be set", p.getClassName().length() > 0);
+  }
+
   @DoNotRunWith(Platform.Htmlunit)
   public void testRadioButton() {
     RadioButton able = widgetUi.myRadioAble;