Overhaul Editor framework traversal logic to use a visitor pattern.
Add a base EditorDriver interface.
Add HasRequestContext interface for better RF+Editor integration.
Patch by: bobv
Review by: rjrjr, tbroyer

Review at http://gwt-code-reviews.appspot.com/1340802


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9666 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/editor/client/EditorContext.java b/user/src/com/google/gwt/editor/client/EditorContext.java
new file mode 100644
index 0000000..9ca0e49
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/EditorContext.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2011 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.editor.client;
+
+/**
+ * Describes an Editor within an Editor hierarchy.
+ * 
+ * @param <T> The type of data edited by the Editor
+ * @see com.google.gwt.editor.client.testing.FakeEditorContext
+ */
+public interface EditorContext<T> {
+  String ROOT_PATH = "";
+
+  /**
+   * Returns a non-null value if the editor returned by {@link #getEditor()}
+   * implements {@link CompositeEditor}.
+   */
+  CompositeEditor<T, ?, ?> asCompositeEditor();
+
+  /**
+   * Returns a non-null value if the editor returned by {@link #getEditor()}
+   * implements {@link HasEditorDelegate}.
+   */
+  HasEditorDelegate<T> asHasEditorDelegate();
+
+  /**
+   * Returns a non-null value if the editor returned by {@link #getEditor()}
+   * implements {@link HasEditorErrors}.
+   */
+  HasEditorErrors<T> asHasEditorErrors();
+
+  /**
+   * Returns a non-null value if the editor returned by {@link #getEditor()}
+   * implements {@link LeafValueEditor}.
+   */
+  LeafValueEditor<T> asLeafValueEditor();
+
+  /**
+   * Returns a non-null value if the editor returned by {@link #getEditor()}
+   * implements {@link ValueAwareEditor}.
+   */
+  ValueAwareEditor<T> asValueAwareEditor();
+
+  /**
+   * Returns {@code true} if {@link #setInModel(Object)} can be called
+   * successfully.
+   */
+  boolean canSetInModel();
+
+  /**
+   * Returns {@code value} cast to the type accepted by the Editor or throws a
+   * {@link ClassCastException}.
+   * 
+   * @param value any value, including {@code null}
+   * @return {@code value} cast to the {@code T} type
+   * @throws ClassCastException if {@code} value is not assignable to the type
+   *           {@code T}
+   */
+  T checkAssignment(Object value);
+
+  /**
+   * Returns the absolute path of the Editor within the hierarchy. This method
+   * should be preferred to calling {@code getEditorDelegate().getPath()}
+   * becasue not all {@link LeafValueEditor LeafValueEditors} are guaranteed to
+   * have an associated delegate.
+   */
+  String getAbsolutePath();
+
+  /**
+   * Returns the {@code T} type.
+   */
+  Class<T> getEditedType();
+
+  /**
+   * Returns the associated Editor.
+   */
+  Editor<T> getEditor();
+
+  /**
+   * Returns the {@link EditorDelegate} associated with the current Editor,
+   * which may be {@code null} for {@link LeafValueEditor LeafValueEditors}.
+   */
+  EditorDelegate<T> getEditorDelegate();
+
+  /**
+   * Returns the value to be edited by the current editor.
+   */
+  T getFromModel();
+
+  /**
+   * Sets a new value in the data hierarchy being edited. The
+   * {@link #checkAssignment(Object)} method may be used to avoid an unsafe
+   * generic cast.
+   */
+  void setInModel(T data);
+
+  /**
+   * Traverse an editor created by
+   * {@link CompositeEditor#createEditorForTraversal()} that reflects an
+   * uninitialized instance of a composite sub-editor. This can be used to
+   * examine the internal structure of a {@link CompositeEditor} even if there
+   * are no data elements being edited by that editor.
+   * 
+   * @throws IllegalStateException if the current Editor is not a
+   *           CompositeEditor
+   */
+  void traverseSyntheticCompositeEditor(EditorVisitor visitor);
+}
diff --git a/user/src/com/google/gwt/editor/client/EditorDriver.java b/user/src/com/google/gwt/editor/client/EditorDriver.java
new file mode 100644
index 0000000..8fff6c6
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/EditorDriver.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 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.editor.client;
+
+import java.util.List;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Defines common capabilities of editor drivers.
+ * <p>
+ * The {@code EditorDriver} interface cannot be used with
+ * {@link com.google.gwt.core.client.GWT#create(Class) GWT.create()} directly.
+ * Instead, use one of the specializations of this interface.
+ * 
+ * @param <T> the type of data returned from {@link #flush()}
+ * @see com.google.gwt.editor.client.SimpleBeanEditorDriver
+ * @see com.google.gwt.requestfactory.client.RequestFactoryEditorDriver
+ */
+public interface EditorDriver<T> {
+  /**
+   * Visit the Editor hierarchy controlled by the EditorDriver.
+   */
+  void accept(EditorVisitor visitor);
+
+  /**
+   * Update the object being edited with the current state of the Editor.
+   * 
+   * @return an implementation-specific value
+   */
+  T flush();
+
+  /**
+   * Returns any unconsumed EditorErrors from the last call to {@link #flush()}.
+   * 
+   * @return a List of {@link EditorError} instances
+   */
+  List<EditorError> getErrors();
+
+  /**
+   * Indicates if the last call to {@link #flush()} resulted in any errors.
+   * 
+   * @return {@code true} if errors are present
+   */
+  boolean hasErrors();
+
+  /**
+   * Returns {@code true} if any of the Editors in the hierarchy have been
+   * modified relative to the last value passed into {@link #edit(Object)}.
+   * <p>
+   * This method is not affected by {@link #flush()} to support the following
+   * workflow:
+   * <ol>
+   * <li>{@code EditorDriver.edit()}</li>
+   * <li>The user edits the on-screen values</li>
+   * <li>{@code EditorDriver.flush()}</li>
+   * <li>The data in the edited object is validated:
+   * <ol>
+   * <li>The validation fails, returning to step 2</li>
+   * <li>The validation succeeds and the editing UI is dismissed</li>
+   * </ol>
+   * </ol>
+   * The simplest implementation of a "navigate away from dirty UI warning" by
+   * checking {@code isDirty()} is correct for the above workflow. If the
+   * {@link #flush()} method were to clear the dirty state, it would be
+   * necessary to implement an alternate flag to distinguish between a
+   * newly-initialized editor entering step 2 or re-entering step 2.
+   * 
+   * @see EditorDelegate#setDirty(boolean)
+   */
+  boolean isDirty();
+
+  /**
+   * Show {@link ConstraintViolation ConstraintViolations} generated through a
+   * {@link javax.validation.Validator Validator}. The violations will be
+   * converted into {@link EditorError} objects whose
+   * {@link EditorError#getUserData() getUserData()} method can be used to
+   * access the original ConstraintViolation object.
+   * 
+   * @param violations an Iterable over {@link ConstraintViolation} instances
+   * @return <code>true</code> if there were any unconsumed EditorErrors which
+   *         can be retrieved from {@link #getErrors()}
+   */
+  boolean setConstraintViolations(Iterable<ConstraintViolation<?>> violations);
+}
diff --git a/user/src/com/google/gwt/editor/client/EditorVisitor.java b/user/src/com/google/gwt/editor/client/EditorVisitor.java
new file mode 100644
index 0000000..1784ad1
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/EditorVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.editor.client;
+
+/**
+ * A visitor for examining an Editor hierarchy.
+ */
+public class EditorVisitor {
+  /**
+   * Exit an Editor. The default implementation is a no-op.
+   * 
+   * @param ctx contextual data about the current Editor
+   */
+  public <T> void endVisit(EditorContext<T> ctx) {
+  }
+
+  /**
+   * Receive an Editor. The default implementation always returns {@code true}.
+   * 
+   * @param ctx contextual data about the current Editor
+   * @return {@code true} if the visitor should visit any sub-editors of the
+   *         current editor.
+   */
+  public <T> boolean visit(EditorContext<T> ctx) {
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java
index 017dcb5..6dffc9e 100644
--- a/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/SimpleBeanEditorDriver.java
@@ -15,10 +15,6 @@
  */
 package com.google.gwt.editor.client;
 
-import java.util.List;
-
-import javax.validation.ConstraintViolation;
-
 /**
  * Automates editing of simple bean-like objects. The {@link EditorDelegate}
  * provided from this driver has a no-op implementation of
@@ -44,7 +40,8 @@
  * @param <E> the Editor for the type
  * @see com.google.gwt.editor.client.testing.MockSimpleBeanEditorDriver
  */
-public interface SimpleBeanEditorDriver<T, E extends Editor<? super T>> {
+public interface SimpleBeanEditorDriver<T, E extends Editor<? super T>> extends
+    EditorDriver<T> {
   /**
    * Push the data in an object graph into the Editor given to
    * {@link #initialize}.
@@ -57,50 +54,15 @@
   /**
    * Update the object being edited with the current state of the Editor.
    * 
-   * @return the object passed into {@link #edit}
-   * @throws IllegalStateException if {@link #edit} has not been called
+   * @return the object passed into {@link #edit(Object)}
+   * @throws IllegalStateException if {@link #edit(Object)} has not been called
    */
   T flush();
 
   /**
-   * Returns any unconsumed EditorErrors from the last call to {@link #flush()}.
-   * 
-   * @return a List of {@link EditorError} instances
-   */
-  List<EditorError> getErrors();
-
-  /**
-   * Indicates if the last call to {@link #flush()} resulted in any errors.
-   * 
-   * @return {@code true} if errors are present
-   */
-  boolean hasErrors();
-
-  /**
    * Initialize the editor driver.
    * 
    * @param editor the Editor to populate
    */
   void initialize(E editor);
-
-  /**
-   * Returns {@code true} if any of the Editors in the hierarchy have been
-   * modified relative to the last value passed into {@link #edit(Object)}.
-   * 
-   * @see EditorDelegate#setDirty(boolean)
-   */
-  boolean isDirty();
-
-  /**
-   * Show {@link ConstraintViolation ConstraintViolations} generated through a
-   * {@link javax.validation.Validator Validator}. The violations will be
-   * converted into {@link EditorError} objects whose
-   * {@link EditorError#getUserData() getUserData()} method can be used to
-   * access the original ConstraintViolation object.
-   * 
-   * @param violations an Iterable over {@link ConstraintViolation} instances
-   * @return <code>true</code> if there were any unconsumed EditorErrors which
-   *         can be retrieved from {@link #getErrors()}
-   */
-  boolean setConstraintViolations(Iterable<ConstraintViolation<?>> violations);
 }
diff --git a/user/src/com/google/gwt/editor/client/adapters/ListEditor.java b/user/src/com/google/gwt/editor/client/adapters/ListEditor.java
index 5a6373e..592a188 100644
--- a/user/src/com/google/gwt/editor/client/adapters/ListEditor.java
+++ b/user/src/com/google/gwt/editor/client/adapters/ListEditor.java
@@ -128,7 +128,11 @@
       // Having entire value reset, so dump the wrapper gracefully
       list.detach();
     }
-    list = new ListEditorWrapper<T, E>(value, chain, editorSource);
-    list.attach();
+    if (value == null) {
+      list = null;
+    } else {
+      list = new ListEditorWrapper<T, E>(value, chain, editorSource);
+      list.attach();
+    }
   }
 }
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractEditorContext.java b/user/src/com/google/gwt/editor/client/impl/AbstractEditorContext.java
new file mode 100644
index 0000000..1b672e6
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractEditorContext.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.editor.client.CompositeEditor;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.HasEditorDelegate;
+import com.google.gwt.editor.client.HasEditorErrors;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.client.ValueAwareEditor;
+
+/**
+ * Base implementation of EditorContext.
+ * 
+ * @param <T> the type of data being edited
+ */
+public abstract class AbstractEditorContext<T> implements EditorContext<T> {
+  private final String path;
+  private final CompositeEditor<?, ?, ?> compositeEditor;
+  private AbstractEditorDelegate<T, ?> delegate;
+  private final Editor<T> editor;
+  private final HasEditorDelegate<T> hasEditorDelegate;
+  private final HasEditorErrors<T> hasEditorErrors;
+  private boolean isHalted;
+  private final LeafValueEditor<T> leafValueEditor;
+  private final ValueAwareEditor<T> valueAwareEditor;
+
+  public AbstractEditorContext(AbstractEditorDelegate<T, ?> delegate) {
+    this(delegate.getEditor(), delegate.getPath());
+    this.delegate = delegate;
+  }
+
+  public AbstractEditorContext(Editor<T> editor, String path) {
+    this.editor = editor;
+    this.path = path;
+    /*
+     * TODO(bobv): Determine if pre-casting is better than demand-casting or
+     * generating the asFoo methods.
+     */
+    compositeEditor = editor instanceof CompositeEditor
+        ? (CompositeEditor<?, ?, ?>) editor : null;
+    hasEditorDelegate = editor instanceof HasEditorDelegate
+        ? (HasEditorDelegate<T>) editor : null;
+    hasEditorErrors = editor instanceof HasEditorErrors
+        ? (HasEditorErrors<T>) editor : null;
+    leafValueEditor = editor instanceof LeafValueEditor
+        ? (LeafValueEditor<T>) editor : null;
+    valueAwareEditor = editor instanceof ValueAwareEditor
+        ? (ValueAwareEditor<T>) editor : null;
+  }
+
+  @SuppressWarnings("unchecked")
+  public CompositeEditor<T, ?, ?> asCompositeEditor() {
+    return (CompositeEditor<T, ?, ?>) compositeEditor;
+  }
+
+  public HasEditorDelegate<T> asHasEditorDelegate() {
+    return hasEditorDelegate;
+  }
+
+  public HasEditorErrors<T> asHasEditorErrors() {
+    return hasEditorErrors;
+  }
+
+  public LeafValueEditor<T> asLeafValueEditor() {
+    return leafValueEditor;
+  }
+
+  public ValueAwareEditor<T> asValueAwareEditor() {
+    return valueAwareEditor;
+  }
+
+  public abstract boolean canSetInModel();
+
+  public abstract T checkAssignment(Object value);
+
+  @SuppressWarnings(value = {"rawtypes", "unchecked"})
+  public void doTraverseSyntheticCompositeEditor(EditorVisitor visitor) {
+    Editor<?> sample = this.asCompositeEditor().createEditorForTraversal();
+    AbstractEditorDelegate subDelegate = delegate.createComposedDelegate();
+    delegate.addSubDelegate(subDelegate, path, sample);
+    delegate.getEditorChain().traverse(visitor, subDelegate);
+  }
+
+  public String getAbsolutePath() {
+    // Not delegate.getPath() since delegate might be null for a leaf editor
+    return path;
+  }
+
+  public abstract Class<T> getEditedType();
+
+  public Editor<T> getEditor() {
+    return editor;
+  }
+
+  public EditorDelegate<T> getEditorDelegate() {
+    return delegate;
+  }
+
+  public abstract T getFromModel();
+
+  public void halt() {
+    isHalted = true;
+  }
+
+  public boolean isHalted() {
+    return isHalted;
+  }
+
+  public abstract void setInModel(T data);
+
+  public void traverse(EditorVisitor visitor, AbstractEditorDelegate<?, ?> next) {
+    if (visitor.visit(this) && next != null) {
+      next.accept(visitor);
+    }
+    visitor.endVisit(this);
+  }
+
+  @Override
+  public void traverseSyntheticCompositeEditor(EditorVisitor visitor) {
+    if (asCompositeEditor() == null) {
+      throw new IllegalStateException();
+    }
+    doTraverseSyntheticCompositeEditor(visitor);
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
index ca3c96b..ed1e52b 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
@@ -19,16 +19,10 @@
 import com.google.gwt.editor.client.Editor;
 import com.google.gwt.editor.client.EditorDelegate;
 import com.google.gwt.editor.client.EditorError;
-import com.google.gwt.editor.client.HasEditorDelegate;
-import com.google.gwt.editor.client.HasEditorErrors;
-import com.google.gwt.editor.client.LeafValueEditor;
-import com.google.gwt.editor.client.ValueAwareEditor;
+import com.google.gwt.editor.client.EditorVisitor;
 import com.google.gwt.event.shared.HandlerRegistration;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -42,38 +36,55 @@
 public abstract class AbstractEditorDelegate<T, E extends Editor<T>> implements
     EditorDelegate<T> {
 
-  private class Chain<R, S extends Editor<R>> implements
+  /**
+   * The machinery for attaching and detaching editors from the hierarchy via a
+   * {@link CompositeEditor}. An instance of a Chain is only created when
+   * necessary for a given hierarchy type.
+   * 
+   * @param <R> the component element type
+   * @param <S> the component editor type
+   */
+  protected class Chain<R, S extends Editor<R>> implements
       CompositeEditor.EditorChain<R, S> {
-    private DelegateMap delegateMap;
-    private Map<S, AbstractEditorDelegate<R, S>> map = new LinkedHashMap<S, AbstractEditorDelegate<R, S>>();
+    private final CompositeEditor<T, R, S> composedEditor;
+    private final Class<R> composedElementType;
+    private final Map<S, AbstractEditorDelegate<R, S>> map = new LinkedHashMap<S, AbstractEditorDelegate<R, S>>();
 
-    public Chain(DelegateMap delegateMap) {
-      this.delegateMap = delegateMap;
+    /**
+     * Constructed via
+     * {@link AbstractEditorDelegate#createChain(CompositeEditor)}.
+     */
+    Chain(CompositeEditor<T, R, S> composedEditor, Class<R> composedElementType) {
+      this.composedEditor = composedEditor;
+      this.composedElementType = composedElementType;
+    }
+
+    public void accept(EditorVisitor visitor) {
+      for (AbstractEditorDelegate<R, S> delegate : map.values()) {
+        traverse(visitor, delegate);
+      }
     }
 
     public void attach(R object, S subEditor) {
       AbstractEditorDelegate<R, S> subDelegate = map.get(subEditor);
 
-      @SuppressWarnings("unchecked")
-      Editor<Object> temp = (Editor<Object>) subEditor;
-      String subPath = path + composedEditor.getPathElement(temp);
+      String subPath = path + composedEditor.getPathElement(subEditor);
 
       if (subDelegate == null) {
-        subDelegate = createComposedDelegate();
+        @SuppressWarnings("unchecked")
+        AbstractEditorDelegate<R, S> temp = (AbstractEditorDelegate<R, S>) createComposedDelegate();
+        subDelegate = temp;
         map.put(subEditor, subDelegate);
-        initializeSubDelegate(subDelegate, subPath, object, subEditor,
-            delegateMap);
+        addSubDelegate(subDelegate, subPath, subEditor);
       } else {
         subDelegate.path = subPath;
-        subDelegate.refresh(object);
       }
+      subDelegate.setObject(ensureMutable(object));
+      traverse(new Initializer(), subDelegate);
     }
 
     public void detach(S subEditor) {
-      AbstractEditorDelegate<R, S> subDelegate = map.remove(subEditor);
-      if (subDelegate != null && subDelegate.shouldFlush()) {
-        subDelegate.flush(errors);
-      }
+      map.remove(subEditor);
     }
 
     public R getValue(S subEditor) {
@@ -81,17 +92,13 @@
       if (subDelegate == null) {
         return null;
       }
-      if (subDelegate.shouldFlush()) {
-        subDelegate.flush(errors);
-      }
       return subDelegate.getObject();
     }
 
-    void collectErrors() {
-      for (AbstractEditorDelegate<?, ?> delegate : map.values()) {
-        errors.addAll(delegate.errors);
-        delegate.errors.clear();
-      }
+    void traverse(EditorVisitor visitor, AbstractEditorDelegate<R, S> delegate) {
+      R object = delegate.getObject();
+      new RootEditorContext<R>(delegate, composedElementType, object).traverse(
+          visitor, delegate);
     }
   }
 
@@ -103,60 +110,12 @@
     }
   }
 
-  protected CompositeEditor<T, Object, Editor<Object>> composedEditor;
-  protected boolean dirty;
-  protected Chain<Object, Editor<Object>> editorChain;
-  protected List<EditorError> errors;
-  protected HasEditorErrors<T> hasEditorErrors;
-  protected T lastLeafValue;
-  /**
-   * Records values last set into any sub-editors that are leaves.
-   */
-  protected Map<String, Object> lastLeafValues;
-  protected LeafValueEditor<T> leafValueEditor;
-  protected String path;
-  /**
-   * A map of local paths to sub-editors that do not have EditorDelegates (and
-   * will not be in the DelegateMap).
-   */
-  protected Map<String, Editor<?>> simpleEditors;
-  /**
-   * This field avoids needing to repeatedly cast {@link #editor}.
-   */
-  protected ValueAwareEditor<T> valueAwareEditor;
+  private boolean dirty;
+  private Chain<?, ?> editorChain;
+  private List<EditorError> errors;
+  private String path;
 
-  /**
-   * Flushes both data and errors.
-   */
-  public void flush(List<EditorError> errorAccumulator) {
-    try {
-      if (valueAwareEditor != null) {
-        valueAwareEditor.flush();
-      }
-
-      if (leafValueEditor != null) {
-        // See comment in initialize about LeafValueEditors
-        setObject(leafValueEditor.getValue());
-        return;
-      }
-
-      if (getObject() == null) {
-        return;
-      }
-      flushSubEditors(errors);
-
-      if (editorChain != null) {
-        editorChain.collectErrors();
-      }
-    } finally {
-      showErrors(errorAccumulator);
-    }
-  }
-
-  public void flushErrors(List<EditorError> errorAccumulator) {
-    flushSubEditorErrors(errorAccumulator);
-    showErrors(errorAccumulator);
-  }
+  public abstract void accept(EditorVisitor visitor);
 
   public abstract T getObject();
 
@@ -164,16 +123,11 @@
     return path;
   }
 
+  /**
+   * Just returns the last value passed to {@link #setDirty(boolean)}.
+   */
   public boolean isDirty() {
-    if (dirty) {
-      return true;
-    }
-    if (leafValueEditor != null) {
-      if (!equals(lastLeafValue, leafValueEditor.getValue())) {
-        return true;
-      }
-    }
-    return isDirtyCheckLeaves();
+    return dirty;
   }
 
   public void recordError(String message, Object value, Object userData) {
@@ -182,30 +136,26 @@
   }
 
   public void recordError(String message, Object value, Object userData,
-      String extraPath) {
+      String extraPath, Editor<?> leafEditor) {
     EditorError error = new SimpleError(this, message, value, userData,
-        extraPath);
+        extraPath, leafEditor);
     errors.add(error);
   }
 
-  public void refresh(T object) {
-    dirty = false;
-    setObject(ensureMutable(object));
-    if (leafValueEditor != null) {
-      lastLeafValue = object;
-      leafValueEditor.setValue(object);
-    } else if (valueAwareEditor != null) {
-      valueAwareEditor.setValue(object);
-    }
-    refreshEditors();
-  }
-
   public void setDirty(boolean dirty) {
     this.dirty = dirty;
   }
 
   public abstract HandlerRegistration subscribe();
 
+  /**
+   * Initialize a sub-delegate whenever one is added to the editor hierarchy.
+   */
+  protected <R, S extends Editor<R>> void addSubDelegate(
+      AbstractEditorDelegate<R, S> subDelegate, String path, S subEditor) {
+    subDelegate.initialize(path, subEditor);
+  }
+
   protected String appendPath(String path) {
     if (path.length() == 0) {
       return this.path;
@@ -213,12 +163,17 @@
     return appendPath(this.path, path);
   }
 
-  protected abstract void attachSubEditors(DelegateMap delegates);
+  protected <R, S extends Editor<R>> void createChain(
+      Class<R> composedElementType) {
+    @SuppressWarnings("unchecked")
+    CompositeEditor<T, R, S> editor = (CompositeEditor<T, R, S>) getEditor();
+    editorChain = new Chain<R, S>(editor, composedElementType);
+  }
 
   /**
    * Only implemented by delegates for a {@link CompositeEditor}.
    */
-  protected <C, D extends Editor<C>> AbstractEditorDelegate<C, D> createComposedDelegate() {
+  protected AbstractEditorDelegate<?, ?> createComposedDelegate() {
     throw new IllegalStateException();
   }
 
@@ -226,106 +181,24 @@
     return object;
   }
 
-  /**
-   * Utility method used by generated subtypes that handles null vs. non-null
-   * comparisons.
-   */
-  protected boolean equals(Object a, Object b) {
-    if (a == b) {
-      return true;
-    }
-    if (a != null && a.equals(b)) {
-      return true;
-    }
-    return false;
-  }
-
-  protected abstract void flushSubEditorErrors(
-      List<EditorError> errorAccumulator);
-
-  protected abstract void flushSubEditors(List<EditorError> errorAccumulator);
-
   protected abstract E getEditor();
 
-  protected Editor<?> getSimpleEditor(String declaredPath) {
-    return simpleEditors.get(declaredPath);
+  protected Chain<?, ?> getEditorChain() {
+    return editorChain;
   }
 
-  /**
-   * Returns {@code true} if the editor contains leaf editors without delegates.
-   */
-  protected abstract boolean hasSubEditorsWithoutDelegates();
+  protected List<EditorError> getErrors() {
+    return errors;
+  }
 
-  protected void initialize(String pathSoFar, T object, E editor,
-      DelegateMap map) {
+  protected void initialize(String pathSoFar, E editor) {
     this.path = pathSoFar;
     setEditor(editor);
-    setObject(ensureMutable(object));
     errors = new ArrayList<EditorError>();
-    simpleEditors = new HashMap<String, Editor<?>>();
-    if (hasSubEditorsWithoutDelegates()) {
-      lastLeafValues = new HashMap<String, Object>();
-    }
-
-    // Set up pre-casted fields to access the editor
-    if (editor instanceof HasEditorErrors<?>) {
-      hasEditorErrors = (HasEditorErrors<T>) editor;
-    }
-    if (editor instanceof LeafValueEditor<?>) {
-      leafValueEditor = (LeafValueEditor<T>) editor;
-    }
-    if (editor instanceof HasEditorDelegate<?>) {
-      ((HasEditorDelegate<T>) editor).setDelegate(this);
-    }
-    if (editor instanceof ValueAwareEditor<?>) {
-      valueAwareEditor = (ValueAwareEditor<T>) editor;
-      if (editor instanceof CompositeEditor<?, ?, ?>) {
-        @SuppressWarnings("unchecked")
-        CompositeEditor<T, Object, Editor<Object>> temp = (CompositeEditor<T, Object, Editor<Object>>) editor;
-        composedEditor = temp;
-        editorChain = new Chain<Object, Editor<Object>>(map);
-        composedEditor.setEditorChain(editorChain);
-      }
-    }
-
-    /*
-     * Unusual case: The user may have installed an editor subtype that adds the
-     * LeafValueEditor interface into a plain Editor field. If this has
-     * happened, only set the value and don't descend into any sub-Editors.
-     */
-    if (leafValueEditor != null) {
-      lastLeafValue = object;
-      leafValueEditor.setValue(object);
-      return;
-    }
-
-    if (valueAwareEditor != null) {
-      valueAwareEditor.setValue(object);
-    }
-
-    if (object != null) {
-      attachSubEditors(map);
-    }
+    initializeSubDelegates();
   }
 
-  /**
-   * Initialize a sub-delegate returned from {@link #createComposedDelegate()}.
-   */
-  protected abstract <R, S extends Editor<R>> void initializeSubDelegate(
-      AbstractEditorDelegate<R, S> subDelegate, String path, R object,
-      S subEditor, DelegateMap map);
-
-  /**
-   * Returns {@code true} if any leaf sub-editors are dirty.
-   * 
-   * @see #lastLeafValues
-   */
-  protected abstract boolean isDirtyCheckLeaves();
-
-  /**
-   * Refresh all of the sub-editors.
-   */
-  protected abstract void refreshEditors();
+  protected abstract void initializeSubDelegates();
 
   protected abstract void setEditor(E editor);
 
@@ -338,47 +211,4 @@
   protected boolean shouldFlush() {
     return true;
   }
-
-  /**
-   * Collect all paths being edited.
-   */
-  protected abstract void traverse(List<String> paths);
-
-  /**
-   * @param errorAccumulator an out parameter to which unhandled EditorErrors
-   *          will be added.
-   */
-  private void showErrors(List<EditorError> errorAccumulator) {
-    assert errors != errorAccumulator;
-    if (hasEditorErrors != null) {
-      // Allow higher-priority co-editor to spy on errors
-      for (Iterator<EditorError> it = errorAccumulator.iterator(); it.hasNext();) {
-        EditorError error = it.next();
-        if (error.getAbsolutePath().startsWith(getPath())) {
-          errors.add(error);
-          it.remove();
-        }
-      }
-      // Include the trailing dot
-      int length = getPath().length();
-      int pathPrefixLength = length == 0 ? 0 : (length + 1);
-      for (EditorError error : errors) {
-        ((SimpleError) error).setPathPrefixLength(pathPrefixLength);
-      }
-      // Give all of the errors to the handler and use a new local accumulator
-      hasEditorErrors.showErrors(Collections.unmodifiableList(errors));
-
-      for (EditorError error : errors) {
-        if (!error.isConsumed()) {
-          errorAccumulator.add(error);
-        }
-      }
-
-      // Reset local error list
-      errors = new ArrayList<EditorError>();
-    } else {
-      errorAccumulator.addAll(errors);
-      errors.clear();
-    }
-  }
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
index 802f86b..728190f 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
@@ -16,14 +16,8 @@
 package com.google.gwt.editor.client.impl;
 
 import com.google.gwt.editor.client.Editor;
-import com.google.gwt.editor.client.EditorError;
 import com.google.gwt.editor.client.SimpleBeanEditorDriver;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.validation.ConstraintViolation;
-
 /**
  * A base implementation class for generated SimpleBeanEditorDriver
  * implementations.
@@ -32,82 +26,18 @@
  * @param <E> the Editor type
  */
 public abstract class AbstractSimpleBeanEditorDriver<T, E extends Editor<T>>
-    implements SimpleBeanEditorDriver<T, E> {
-
-  private SimpleBeanEditorDelegate<T, E> delegate;
-  private DelegateMap delegateMap = new DelegateMap(DelegateMap.IDENTITY);
-  private E editor;
-  private List<EditorError> errors;
-  private T object;
+    extends BaseEditorDriver<T, E> implements SimpleBeanEditorDriver<T, E> {
 
   public void edit(T object) {
-    checkEditor();
-    this.object = object;
-    delegate = createDelegate();
-    delegate.initialize("", object, editor, delegateMap);
-    delegateMap.put(object, delegate);
+    doEdit(object);
   }
 
   public T flush() {
-    checkDelegate();
-    errors = new ArrayList<EditorError>();
-    delegate.flush(errors);
-    return object;
-  }
-
-  public List<EditorError> getErrors() {
-    return errors;
-  }
-
-  public boolean hasErrors() {
-    return !errors.isEmpty();
+    doFlush();
+    return getObject();
   }
 
   public void initialize(E editor) {
-    this.editor = editor;
+    doInitialize(editor);
   }
-
-  public boolean isDirty() {
-    for (AbstractEditorDelegate<?, ?> d : delegateMap) {
-      if (d.isDirty()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public boolean setConstraintViolations(
-      final Iterable<ConstraintViolation<?>> violations) {
-    checkDelegate();
-    SimpleViolation.pushViolations(
-        SimpleViolation.iterableFromConstrantViolations(violations),
-        delegateMap);
-
-    // Flush the errors, which will take care of co-editor chains.
-    errors = new ArrayList<EditorError>();
-    delegate.flushErrors(errors);
-    return hasErrors();
-  }
-
-  protected abstract SimpleBeanEditorDelegate<T, E> createDelegate();
-
-  /**
-   * Visible for testing.
-   */
-  DelegateMap getDelegateMap() {
-    return delegateMap;
-  }
-
-  private void checkDelegate() {
-    if (delegate == null) {
-      throw new IllegalStateException("Must call edit() first");
-    }
-  }
-
-  private void checkEditor() {
-    if (editor == null) {
-      throw new IllegalStateException("Must call initialize() first");
-    }
-  }
-
 }
diff --git a/user/src/com/google/gwt/editor/client/impl/BaseEditorDriver.java b/user/src/com/google/gwt/editor/client/impl/BaseEditorDriver.java
new file mode 100644
index 0000000..dddb011
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/BaseEditorDriver.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDriver;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.client.impl.DelegateMap.KeyMethod;
+import com.google.gwt.editor.client.testing.EditorHierarchyPrinter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Contains common code shared between the SimpleBeanEditorDriver and
+ * RequestFactoryEditorDriver.
+ * 
+ * @param <T> the type of data being edited
+ * @param <E> the type of editor
+ */
+public abstract class BaseEditorDriver<T, E extends Editor<T>> {
+  private AbstractEditorDelegate<T, E> delegate;
+  /**
+   * Used for {@link #isDirty()} computations.
+   */
+  private Map<LeafValueEditor<?>, Object> leafValueMap;
+  private E editor;
+  private List<EditorError> errors;
+  private T object;
+
+  public abstract void accept(EditorVisitor visitor);
+
+  public List<EditorError> getErrors() {
+    return errors;
+  }
+
+  public boolean hasErrors() {
+    return !errors.isEmpty();
+  }
+
+  public boolean isDirty() {
+    DirtCollector c = new DirtCollector();
+    accept(c);
+    return c.isDirty() || !leafValueMap.equals(c.getLeafValues());
+  }
+
+  public boolean setConstraintViolations(
+      final Iterable<ConstraintViolation<?>> violations) {
+    return doSetViolations(SimpleViolation.iterableFromConstrantViolations(violations));
+  }
+
+  @Override
+  public String toString() {
+    if (GWT.isProdMode()) {
+      return super.toString();
+    } else {
+      return editor == null ? "Uninitialized"
+          : EditorHierarchyPrinter.toString(asEditorDriver());
+    }
+  }
+
+  protected void configureDelegate(AbstractEditorDelegate<T, E> rootDelegate) {
+    rootDelegate.initialize("", getEditor());
+  }
+
+  protected abstract AbstractEditorDelegate<T, E> createDelegate();
+
+  protected void doEdit(T object) {
+    checkEditor();
+    object = delegate.ensureMutable(object);
+    this.object = object;
+    delegate.setObject(object);
+    accept(new Initializer());
+    DirtCollector c = new DirtCollector();
+    accept(c);
+    leafValueMap = c.getLeafValues();
+  }
+
+  protected void doFlush() {
+    checkObject();
+    errors = new ArrayList<EditorError>();
+    accept(new Flusher());
+    accept(new ErrorCollector(errors));
+  }
+
+  protected void doInitialize(E editor) {
+    this.editor = editor;
+    delegate = createDelegate();
+    configureDelegate(delegate);
+  }
+
+  protected boolean doSetViolations(Iterable<SimpleViolation> violations) {
+    checkObject();
+    SimpleViolation.pushViolations(violations, asEditorDriver(),
+        getViolationKeyMethod());
+
+    // Collect the errors, which will take care of co-editor chains.
+    errors = new ArrayList<EditorError>();
+    accept(new ErrorCollector(errors));
+    return hasErrors();
+  }
+
+  protected AbstractEditorDelegate<T, E> getDelegate() {
+    return delegate;
+  }
+
+  protected E getEditor() {
+    return editor;
+  }
+
+  protected T getObject() {
+    return object;
+  }
+
+  protected KeyMethod getViolationKeyMethod() {
+    return DelegateMap.IDENTITY;
+  }
+
+  /**
+   * This cast avoids the need to add another parameterization to
+   * BaseEditorDriver since the class cannot be declared to extend an unbound
+   * interface.
+   */
+  private EditorDriver<?> asEditorDriver() {
+    return (EditorDriver<?>) this;
+  }
+
+  private void checkEditor() {
+    if (editor == null) {
+      throw new IllegalStateException("Must call initialize() first");
+    }
+  }
+
+  private void checkObject() {
+    if (object == null) {
+      throw new IllegalStateException("Must call edit() first");
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/impl/DelegateMap.java b/user/src/com/google/gwt/editor/client/impl/DelegateMap.java
index fa336ab..b5e22bd 100644
--- a/user/src/com/google/gwt/editor/client/impl/DelegateMap.java
+++ b/user/src/com/google/gwt/editor/client/impl/DelegateMap.java
@@ -15,6 +15,11 @@
  */
 package com.google.gwt.editor.client.impl;
 
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorDriver;
+import com.google.gwt.editor.client.EditorVisitor;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -26,7 +31,8 @@
  */
 public class DelegateMap implements Iterable<AbstractEditorDelegate<?, ?>> {
   /**
-   * 
+   * Defines an equivalence relationship to allow objects with non-identity
+   * equality to be used as data keys.
    */
   public interface KeyMethod {
     Object key(Object object);
@@ -79,11 +85,29 @@
     }
   };
 
+  public static DelegateMap of(EditorDriver<?> driver, KeyMethod key) {
+    final DelegateMap toReturn = new DelegateMap(key);
+    driver.accept(new EditorVisitor() {
+      @Override
+      public <T> void endVisit(EditorContext<T> ctx) {
+        toReturn.put(ctx.getAbsolutePath(), ctx.getEditor());
+        @SuppressWarnings("unchecked")
+        AbstractEditorDelegate<T, ?> delegate = (AbstractEditorDelegate<T, ?>) ctx.getEditorDelegate();
+        if (delegate != null) {
+          toReturn.put(delegate.getObject(), delegate);
+        }
+      }
+    });
+    return toReturn;
+  }
+
   private final Map<Object, List<AbstractEditorDelegate<?, ?>>> map = new HashMap<Object, List<AbstractEditorDelegate<?, ?>>>();
-  private final Map<String, List<AbstractEditorDelegate<?, ?>>> paths = new HashMap<String, List<AbstractEditorDelegate<?, ?>>>();
+  private final Map<String, List<AbstractEditorDelegate<?, ?>>> delegatesByPath = new HashMap<String, List<AbstractEditorDelegate<?, ?>>>();
+  private final Map<String, List<Editor<?>>> editorsByPath = new HashMap<String, List<Editor<?>>>();
+
   private final KeyMethod keyMethod;
 
-  public DelegateMap(KeyMethod key) {
+  DelegateMap(KeyMethod key) {
     this.keyMethod = key;
   }
 
@@ -93,10 +117,17 @@
   }
 
   /**
+   * Returns a list of EditorDelegates available at a particular absolute path.
+   */
+  public List<AbstractEditorDelegate<?, ?>> getDelegatesByPath(String path) {
+    return delegatesByPath.get(path);
+  }
+
+  /**
    * Returns a list of Editors available at a particular absolute path.
    */
-  public List<AbstractEditorDelegate<?, ?>> getPath(String path) {
-    return paths.get(path);
+  public List<Editor<?>> getEditorByPath(String path) {
+    return editorsByPath.get(path);
   }
 
   /**
@@ -110,26 +141,27 @@
     return new MapIterator(this);
   }
 
-  public <T> void put(T object, AbstractEditorDelegate<T, ?> delegate) {
-    {
-      List<AbstractEditorDelegate<?, ?>> list = paths.get(delegate.getPath());
-      if (list == null) {
-        list = new ArrayList<AbstractEditorDelegate<?, ?>>();
-        paths.put(delegate.getPath(), list);
-      }
-      list.add(delegate);
+  <K, V> void add(Map<K, List<V>> map, K key, V value) {
+    List<V> list = map.get(key);
+    if (list == null) {
+      list = new ArrayList<V>();
+      map.put(key, list);
     }
+    list.add(value);
+  }
+
+  <T> void put(String path, Editor<T> editor) {
+    add(editorsByPath, path, editor);
+  }
+
+  <T> void put(T object, AbstractEditorDelegate<T, ?> delegate) {
+    add(delegatesByPath, delegate.getPath(), delegate);
+
     Object key = keyMethod.key(object);
     if (key == null) {
       return;
     }
-    {
-      List<AbstractEditorDelegate<?, ?>> list = map.get(key);
-      if (list == null) {
-        list = new ArrayList<AbstractEditorDelegate<?, ?>>();
-        map.put(key, list);
-      }
-      list.add(delegate);
-    }
+
+    add(map, key, delegate);
   }
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/DirtCollector.java b/user/src/com/google/gwt/editor/client/impl/DirtCollector.java
new file mode 100644
index 0000000..79aaae8
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/DirtCollector.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.LeafValueEditor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class DirtCollector extends EditorVisitor {
+  public boolean dirty;
+  private final Map<LeafValueEditor<?>, Object> leafValues = new HashMap<LeafValueEditor<?>, Object>();
+
+  @Override
+  public <T> void endVisit(EditorContext<T> ctx) {
+    LeafValueEditor<T> editor = ctx.asLeafValueEditor();
+    if (editor != null) {
+      leafValues.put(editor, editor.getValue());
+    }
+    @SuppressWarnings("unchecked")
+    AbstractEditorDelegate<T, ?> delegate = (AbstractEditorDelegate<T, ?>) ctx.getEditorDelegate();
+    if (delegate != null) {
+      dirty |= delegate.isDirty();
+    }
+  }
+
+  public Map<LeafValueEditor<?>, Object> getLeafValues() {
+    return leafValues;
+  }
+
+  /**
+   * Returns {@code true} if
+   * {@link com.google.gwt.editor.client.EditorDelegate#setDirty(boolean)} was
+   * used.
+   */
+  public boolean isDirty() {
+    return dirty;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/ErrorCollector.java b/user/src/com/google/gwt/editor/client/impl/ErrorCollector.java
new file mode 100644
index 0000000..a51c62a
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/ErrorCollector.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.HasEditorErrors;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * Collects and propagates EditorErrors through an Editor hierarchy.
+ */
+class ErrorCollector extends EditorVisitor {
+  private final Stack<List<EditorError>> errorStack = new Stack<List<EditorError>>();
+  private String lastAddedPath;
+  private List<EditorError> lastAdded;
+
+  public ErrorCollector(List<EditorError> errors) {
+    assert errors != null;
+    errorStack.push(errors);
+  }
+
+  @Override
+  public <T> void endVisit(EditorContext<T> ctx) {
+    AbstractEditorDelegate<?, ?> delegate = (AbstractEditorDelegate<?, ?>) ctx.getEditorDelegate();
+    if (delegate == null) {
+      return;
+    }
+
+    // Collect errors
+    List<EditorError> errors = delegate.getErrors();
+    lastAdded = new ArrayList<EditorError>(errors);
+    lastAddedPath = ctx.getAbsolutePath();
+    errorStack.peek().addAll(errors);
+    errors.clear();
+
+    // Filter collected errors through an error-aware editor
+    HasEditorErrors<T> asErrors = ctx.asHasEditorErrors();
+    if (asErrors != null) {
+      // Get the enclosing error domain
+      List<EditorError> tryConsume = errorStack.pop();
+      int prefixLength = ctx.getAbsolutePath().length();
+      // Remove trailing dot in non-empty paths
+      prefixLength = prefixLength == 0 ? 0 : prefixLength + 1;
+      for (EditorError error : tryConsume) {
+        ((SimpleError) error).setPathPrefixLength(prefixLength);
+      }
+      /*
+       * Pass collected errors to the editor. Must pass empty error collection
+       * to the editor so that it can clear any existing errors when problems
+       * are fixed.
+       */
+      asErrors.showErrors(tryConsume);
+
+      // Short-circuit if there are no existing errors
+      if (!tryConsume.isEmpty()) {
+        List<EditorError> accumulator = errorStack.peek();
+        for (EditorError e : tryConsume) {
+          // Pass unconsumed error to enclosing domain
+          if (!e.isConsumed()) {
+            accumulator.add(e);
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public <Q> boolean visit(EditorContext<Q> ctx) {
+    // Create a new "domain" for each error-aware editor
+    HasEditorErrors<Q> asErrors = ctx.asHasEditorErrors();
+    if (asErrors != null) {
+      /*
+       * Aliased editors (like ValueBoxEditorDecorator) will see the errors for
+       * an editor at the same path that it occupies. If the editor that we're
+       * currently looking at has the same path as the last thing we just saw,
+       * recycle the previous errors.
+       */
+      if (ctx.getAbsolutePath().equals(lastAddedPath)) {
+        errorStack.peek().removeAll(lastAdded);
+        errorStack.push(lastAdded);
+      } else {
+        errorStack.push(new ArrayList<EditorError>());
+      }
+    }
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/impl/Flusher.java b/user/src/com/google/gwt/editor/client/impl/Flusher.java
new file mode 100644
index 0000000..4b682b7
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/Flusher.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.client.ValueAwareEditor;
+
+import java.util.Stack;
+
+/**
+ * Copies data from the Editor hierarchy into the backing objects.
+ */
+class Flusher extends EditorVisitor {
+  private final Stack<AbstractEditorDelegate<?, ?>> delegateStack = new Stack<AbstractEditorDelegate<?, ?>>();
+
+  @Override
+  public <Q> void endVisit(EditorContext<Q> ctx) {
+    // Flush ValueAware editors
+    ValueAwareEditor<Q> asValue = ctx.asValueAwareEditor();
+
+    AbstractEditorDelegate<?, ?> delegate;
+    if (ctx.getEditorDelegate() == null) {
+      delegate = delegateStack.peek();
+    } else {
+      delegate = delegateStack.pop();
+    }
+    assert delegate != null;
+
+    if (asValue != null) {
+      assert delegate != null : "ValueAwareEditor without delegate";
+      if (delegate.shouldFlush()) {
+        asValue.flush();
+      }
+    }
+
+    // Pull value from LeafValueEditors and update edited object
+    LeafValueEditor<Q> asLeaf = ctx.asLeafValueEditor();
+    if (delegate.shouldFlush() && asLeaf != null && ctx.canSetInModel()) {
+      ctx.setInModel(asLeaf.getValue());
+    }
+  }
+
+  @Override
+  public <Q> boolean visit(EditorContext<Q> ctx) {
+    EditorDelegate<Q> editorDelegate = ctx.getEditorDelegate();
+    if (editorDelegate != null) {
+      delegateStack.push((AbstractEditorDelegate<?, ?>) editorDelegate);
+    }
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/Initializer.java b/user/src/com/google/gwt/editor/client/impl/Initializer.java
new file mode 100644
index 0000000..84168a5
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/Initializer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.editor.client.CompositeEditor;
+import com.google.gwt.editor.client.CompositeEditor.EditorChain;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.HasEditorDelegate;
+
+/**
+ * Extends the logic in Refresher to provide the editor instance with references
+ * to framework plumbing fixes.
+ */
+class Initializer extends Refresher {
+
+  @Override
+  public <Q> boolean visit(EditorContext<Q> ctx) {
+    @SuppressWarnings("unchecked")
+    AbstractEditorDelegate<Q, ?> delegate = (AbstractEditorDelegate<Q, ?>) ctx.getEditorDelegate();
+
+    // Pass in the EditorDelegate
+    HasEditorDelegate<Q> asHasDelegate = ctx.asHasEditorDelegate();
+    if (asHasDelegate != null) {
+      asHasDelegate.setDelegate(delegate);
+    }
+
+    // Set the EditorChain
+    CompositeEditor<Q, ?, ?> asComposite = ctx.asCompositeEditor();
+    if (asComposite != null) {
+      // Various javac generics compilation problems here
+      @SuppressWarnings("rawtypes")
+      EditorChain chain = delegate.getEditorChain();
+      asComposite.setEditorChain(chain);
+    }
+
+    return super.visit(ctx);
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/Refresher.java b/user/src/com/google/gwt/editor/client/impl/Refresher.java
new file mode 100644
index 0000000..dd34bde
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/Refresher.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.client.ValueAwareEditor;
+
+/**
+ * A lightweight peer to {@link Initializer} which simply resets the values in
+ * the editor and delegate hiererchy.
+ */
+public class Refresher extends EditorVisitor {
+  public <Q> boolean visit(EditorContext<Q> ctx) {
+    Q toSet = ctx.getFromModel();
+    @SuppressWarnings("unchecked")
+    AbstractEditorDelegate<Q, ?> delegate = (AbstractEditorDelegate<Q, ?>) ctx.getEditorDelegate();
+    if (delegate != null) {
+      delegate.setObject(delegate.ensureMutable(toSet));
+      delegate.setDirty(false);
+    }
+    ValueAwareEditor<Q> asValue = ctx.asValueAwareEditor();
+    if (asValue != null) {
+      // Call setValue for ValueAware, non-leaf editors
+      asValue.setValue(toSet);
+    } else {
+      LeafValueEditor<Q> asLeaf = ctx.asLeafValueEditor();
+      if (asLeaf != null) {
+        // Call setvalue for LeafValueEditors.
+        asLeaf.setValue(toSet);
+      }
+    }
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/RootEditorContext.java b/user/src/com/google/gwt/editor/client/impl/RootEditorContext.java
new file mode 100644
index 0000000..5f82cda
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/RootEditorContext.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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.editor.client.impl;
+
+/**
+ * An implementation of {@code EditorContext} that is used as a starting point
+ * by EditorDrivers.
+ * 
+ * @param <T> the type of data being edited
+ */
+public class RootEditorContext<T> extends AbstractEditorContext<T> {
+
+  private final Class<T> editedType;
+  private final T value;
+
+  public RootEditorContext(AbstractEditorDelegate<T, ?> editorDelegate,
+      Class<T> editedType, T value) {
+    super(editorDelegate);
+    this.editedType = editedType;
+    this.value = value;
+  }
+
+  @Override
+  public boolean canSetInModel() {
+    return true;
+  }
+
+  /**
+   * Returns {@code value}. There's no way to actually implement this check in
+   * GWT client-side code. In order to prevent users from having to check for
+   * the root context in a visitor to avoid a special case, we'll just return
+   * the object. If the value really isn't assignable to T, a call to
+   * {@link #setInModel(Object)} will fail in the generated code, which has an
+   * explicit cast.
+   */
+  @Override
+  @SuppressWarnings("unchecked")
+  public T checkAssignment(Object value) {
+    return (T) value;
+  }
+
+  @Override
+  public Class<T> getEditedType() {
+    return editedType;
+  }
+
+  @Override
+  public T getFromModel() {
+    return value;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public void setInModel(T data) {
+    ((AbstractEditorDelegate<T, ?>) getEditorDelegate()).setObject(data);
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java b/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java
index 5fb855f..2ccfd47 100644
--- a/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java
+++ b/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java
@@ -28,20 +28,7 @@
     AbstractEditorDelegate<T, E> {
 
   @Override
-  public void initialize(String pathSoFar, T object, E editor, DelegateMap map) {
-    super.initialize(pathSoFar, object, editor, map);
-  }
-
-  @Override
   public HandlerRegistration subscribe() {
     return null;
   }
-
-  @Override
-  protected <R, S extends Editor<R>> void initializeSubDelegate(
-      AbstractEditorDelegate<R, S> subDelegate, String path, R object,
-      S subEditor, DelegateMap map) {
-    ((SimpleBeanEditorDelegate<R, S>) subDelegate).initialize(path, object,
-        subEditor, map);
-  }
 }
diff --git a/user/src/com/google/gwt/editor/client/impl/SimpleError.java b/user/src/com/google/gwt/editor/client/impl/SimpleError.java
index e2cf710..83ea7da 100644
--- a/user/src/com/google/gwt/editor/client/impl/SimpleError.java
+++ b/user/src/com/google/gwt/editor/client/impl/SimpleError.java
@@ -43,10 +43,10 @@
    * Used to construct an error for an Editor that does not have a delegate.
    */
   SimpleError(AbstractEditorDelegate<?, ?> delegate, String message,
-      Object value, Object userData, String extraPath) {
+      Object value, Object userData, String extraPath, Editor<?> leafEditor) {
     assert extraPath != null && extraPath.length() > 0;
     this.absolutePath = delegate.getPath() + extraPath;
-    this.editor = delegate.getSimpleEditor(extraPath);
+    this.editor = leafEditor;
     this.message = message;
     this.value = value;
     this.userData = userData;
diff --git a/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java b/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java
index b297bac..bfa4d03 100644
--- a/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java
+++ b/user/src/com/google/gwt/editor/client/impl/SimpleViolation.java
@@ -15,6 +15,10 @@
  */
 package com.google.gwt.editor.client.impl;
 
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDriver;
+import com.google.gwt.editor.client.impl.DelegateMap.KeyMethod;
+
 import java.util.Iterator;
 import java.util.List;
 
@@ -100,7 +104,9 @@
    * EditorDelegate.
    */
   public static void pushViolations(Iterable<SimpleViolation> violations,
-      DelegateMap delegateMap) {
+      EditorDriver<?> driver, KeyMethod keyMethod) {
+    DelegateMap delegateMap = DelegateMap.of(driver, keyMethod);
+
     // For each violation
     for (SimpleViolation error : violations) {
       Object key = error.getKey();
@@ -118,15 +124,19 @@
               + error.getPath();
 
           // Find the leaf editor's delegate.
-          List<AbstractEditorDelegate<?, ?>> leafDelegates = delegateMap.getPath(absolutePath);
+          List<AbstractEditorDelegate<?, ?>> leafDelegates = delegateMap.getDelegatesByPath(absolutePath);
+          List<Editor<?>> editors = delegateMap.getEditorByPath(absolutePath);
           if (leafDelegates != null) {
-            // Only attach the error to the first delegate in a co-editor chain.
-            leafDelegates.get(0).recordError(error.getMessage(), null,
-                error.getUserDataObject());
-          } else {
-            // No EditorDelegate to attach it to, stick it on the base.
-            baseDelegate.recordError(error.getMessage(), null,
-                error.getUserDataObject(), error.getPath());
+            for (AbstractEditorDelegate<?, ?> delegate : leafDelegates) {
+              delegate.recordError(error.getMessage(), null,
+                  error.getUserDataObject());
+            }
+          } else if (editors != null) {
+            // No EditorDelegate to attach it to, so fake the source
+            for (Editor<?> editor : editors) {
+              baseDelegate.recordError(error.getMessage(), null,
+                  error.getUserDataObject(), error.getPath(), editor);
+            }
           }
         }
       }
diff --git a/user/src/com/google/gwt/editor/client/testing/EditorHierarchyPrinter.java b/user/src/com/google/gwt/editor/client/testing/EditorHierarchyPrinter.java
new file mode 100644
index 0000000..f7e1759
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/testing/EditorHierarchyPrinter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011 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.editor.client.testing;
+
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorDriver;
+import com.google.gwt.editor.client.EditorVisitor;
+
+/**
+ * A utility class that creates a string representation of an Editor hierarchy
+ * for testing purposes.
+ */
+public final class EditorHierarchyPrinter extends EditorVisitor {
+  private static final String INDENT = "  ";
+  private static final String SPACE = " ";
+
+  /**
+   * Produce a string representation of the Editor hierarchy being controlled by
+   * {@code driver}.
+   */
+  public static String toString(EditorDriver<?> driver) {
+    StringBuilder sb = new StringBuilder();
+    driver.accept(new EditorHierarchyPrinter(sb));
+    return sb.toString();
+  }
+
+  private int level = 0;
+  private final StringBuilder sb;
+
+  private EditorHierarchyPrinter(StringBuilder out) {
+    this.sb = out;
+  }
+
+  @Override
+  public <T> void endVisit(EditorContext<T> ctx) {
+    level--;
+  }
+
+  @Override
+  public <T> boolean visit(EditorContext<T> ctx) {
+    println(ctx.getAbsolutePath());
+    data(ctx.getEditedType().getName());
+    data(ctx.getEditor().getClass().getName());
+    data("Implements: " //
+        + ctx.asCompositeEditor() == null ? "" : "CompositeEditor " //
+        + ctx.asHasEditorDelegate() == null ? "" : "HasEditorDelegate " //
+        + ctx.asHasEditorErrors() == null ? "" : "HasEditorErrors " //
+        + ctx.asLeafValueEditor() == null ? "" : "LeafValueEditor " //
+        + ctx.asValueAwareEditor() == null ? "" : "ValueAwareEditor ");
+    level++;
+    return true;
+  }
+
+  private void data(String msg) {
+    println(SPACE + msg);
+  }
+
+  private void println(String msg) {
+    for (int i = 0; i < level; i++) {
+      sb.append(INDENT);
+    }
+    sb.append(msg);
+    sb.append("\n");
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/testing/FakeEditorContext.java b/user/src/com/google/gwt/editor/client/testing/FakeEditorContext.java
new file mode 100644
index 0000000..0cf7891
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/testing/FakeEditorContext.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2011 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.editor.client.testing;
+
+import com.google.gwt.editor.client.CompositeEditor;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.HasEditorDelegate;
+import com.google.gwt.editor.client.HasEditorErrors;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.client.ValueAwareEditor;
+
+/**
+ * A no-op implementation of EditorContext for testing.
+ * 
+ * @param <T> the type of data not being edited
+ */
+public class FakeEditorContext<T> implements EditorContext<T> {
+
+  /**
+   * Returns {@code null}.
+   */
+  public CompositeEditor<T, ?, ?> asCompositeEditor() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public HasEditorDelegate<T> asHasEditorDelegate() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public HasEditorErrors<T> asHasEditorErrors() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public LeafValueEditor<T> asLeafValueEditor() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public ValueAwareEditor<T> asValueAwareEditor() {
+    return null;
+  }
+
+  /**
+   * Returns {@code false}.
+   */
+  public boolean canSetInModel() {
+    return false;
+  }
+
+  /**
+   * Returns {@code value} via an unchecked generic cast.
+   */
+  @SuppressWarnings("unchecked")
+  public T checkAssignment(Object value) {
+    return (T) value;
+  }
+
+  /**
+   * Returns {@link EditorContext#ROOT_PATH}.
+   */
+  public String getAbsolutePath() {
+    return ROOT_PATH;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public Class<T> getEditedType() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public Editor<T> getEditor() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public EditorDelegate<T> getEditorDelegate() {
+    return null;
+  }
+
+  /**
+   * Returns {@code null}.
+   */
+  public T getFromModel() {
+    return null;
+  }
+
+  /**
+   * A no-op.
+   */
+  public void setInModel(T data) {
+  }
+
+  /**
+   * No-op.
+   */
+  @Override
+  public void traverseSyntheticCompositeEditor(EditorVisitor visitor) {
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java
index c098255..6e2556c 100644
--- a/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/testing/MockSimpleBeanEditorDriver.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.editor.client.Editor;
 import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorVisitor;
 import com.google.gwt.editor.client.SimpleBeanEditorDriver;
 
 import java.util.Collections;
@@ -38,6 +39,12 @@
   private T object;
 
   /**
+   * A no-op method.
+   */
+  public void accept(EditorVisitor visitor) {
+  }
+
+  /**
    * Records <code>object</code>.
    */
   public void edit(T object) {
diff --git a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
index e7ff8b7..a080e37 100644
--- a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
@@ -25,7 +25,10 @@
 import com.google.gwt.dev.util.Name;
 import com.google.gwt.dev.util.Name.BinaryName;
 import com.google.gwt.editor.client.Editor;
-import com.google.gwt.editor.client.impl.DelegateMap;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.impl.AbstractEditorContext;
+import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.RootEditorContext;
 import com.google.gwt.editor.rebind.model.EditorData;
 import com.google.gwt.editor.rebind.model.EditorModel;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -33,7 +36,6 @@
 
 import java.io.PrintWriter;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -128,20 +130,20 @@
        * supertype.
        */
       sw.println("private %s editor;", editor.getQualifiedSourceName());
-      sw.println("protected %s getEditor() {return editor;}",
+      sw.println("@Override protected %s getEditor() {return editor;}",
           editor.getQualifiedSourceName());
       sw.println(
           "protected void setEditor(%s editor) {this.editor=(%s)editor;}",
           Editor.class.getCanonicalName(), editor.getQualifiedSourceName());
       sw.println("private %s object;", edited.getQualifiedSourceName());
-      sw.println("public %s getObject() {return object;}",
+      sw.println("@Override public %s getObject() {return object;}",
           edited.getQualifiedSourceName());
       sw.println(
-          "protected void setObject(Object object) {this.object=(%s)object;}",
+          "@Override protected void setObject(Object object) {this.object=(%s)object;}",
           edited.getQualifiedSourceName());
 
       if (delegateData.isCompositeEditor()) {
-        sw.println("protected %s createComposedDelegate() {",
+        sw.println("@Override protected %s createComposedDelegate() {",
             Name.getSourceNameForClass(this.getEditorDelegateType()));
         sw.indentln("return new %s();",
             getEditorDelegate(delegateData.getComposedData()));
@@ -160,224 +162,55 @@
       }
 
       // For each entity property, create a sub-delegate and initialize
-      sw.println("protected void attachSubEditors(%s delegateMap) {",
-          DelegateMap.class.getCanonicalName());
+      sw.println("@Override protected void initializeSubDelegates() {");
       sw.indent();
+      if (delegateData.isCompositeEditor()) {
+        sw.println(
+            "createChain(%s.class);",
+            delegateData.getComposedData().getEditedType().getQualifiedSourceName());
+      }
       for (EditorData d : data) {
         String subDelegateType = getEditorDelegate(d);
-        sw.println("if (editor.%s != null) {", d.getSimpleExpression());
-        sw.indent();
         if (d.isDelegateRequired()) {
-          sw.println("%s = new %s();", delegateFields.get(d), subDelegateType);
-          writeDelegateInitialization(sw, d, delegateFields);
-          sw.println("delegateMap.put(%1$s.getObject(), %1$s);",
-              delegateFields.get(d));
-        } else if (d.isLeafValueEditor()) {
-          // if (can().access().without().npe()) {
-          sw.println("if (%s) {", d.getBeanOwnerGuard("getObject()"));
+          sw.println("if (editor.%s != null) {", d.getSimpleExpression());
           sw.indent();
-          // Bar value = getObject()....;
-          sw.println("%s value = getObject()%s%s;",
-              d.getEditedType().getQualifiedSourceName(),
-              d.getBeanOwnerExpression(), d.getGetterExpression());
-          // editor.subEditor.setValue(value);
-          sw.println("editor.%s.setValue(value);", d.getSimpleExpression());
-          // simpleEditors.put("foo.bar", editor.subEditor);
-          sw.println("simpleEditors.put(\"%s\", editor.%s);",
-              d.getDeclaredPath(), d.getSimpleExpression());
-          // lastLeafValues.put("foo.bar", value);
-          sw.println("lastLeafValues.put(\"%s\", value);", d.getDeclaredPath());
+          sw.println("%s = new %s();", delegateFields.get(d), subDelegateType);
+          sw.println("addSubDelegate(%s, appendPath(\"%s\"), editor.%s);",
+              delegateFields.get(d), d.getPropertyName(),
+              d.getSimpleExpression());
           sw.outdent();
           sw.println("}");
         }
+      }
+      sw.outdent();
+      sw.println("}");
+
+      sw.println("@Override public void accept(%s visitor) {",
+          EditorVisitor.class.getCanonicalName());
+      sw.indent();
+      if (delegateData.isCompositeEditor()) {
+        sw.println("getEditorChain().accept(visitor);");
+      }
+      for (EditorData d : data) {
+        sw.println("{");
+        sw.indent();
+        String editorContextName = getEditorContext(delegateData, d);
+        if (d.isDelegateRequired()) {
+          sw.println("%s ctx = new %s(getObject(), %s);", editorContextName,
+              editorContextName, delegateFields.get(d));
+        } else {
+          sw.println(
+              "%s ctx = new %s(getObject(), editor.%s, appendPath(\"%s\"));",
+              editorContextName, editorContextName, d.getSimpleExpression(),
+              d.getDeclaredPath());
+        }
+        sw.println("ctx.traverse(visitor, %s);", d.isDelegateRequired()
+            ? delegateFields.get(d) : "null");
         sw.outdent();
         sw.println("}");
       }
       sw.outdent();
       sw.println("}");
-
-      // Flush each sub-delegate
-      sw.println("protected void flushSubEditors(%s errorAccumulator) {",
-          List.class.getCanonicalName());
-      sw.indent();
-      for (EditorData d : data) {
-        String mutableObjectExpression;
-        if (d.getBeanOwnerExpression().length() > 0) {
-          mutableObjectExpression = mutableObjectExpression(d,
-              String.format("(getObject()%s)", d.getBeanOwnerExpression()));
-        } else {
-          mutableObjectExpression = "getObject()";
-        }
-
-        if (d.getSetterName() != null && d.isLeafValueEditor()) {
-          // if (editor.subEditor != null && can().access()) {
-          sw.println("if (editor.%s != null && %s) {", d.getSimpleExpression(),
-              d.getBeanOwnerGuard("getObject()"));
-          sw.indent();
-          if (d.isDelegateRequired()) {
-            sw.println("%s.flush(errorAccumulator);", delegateFields.get(d));
-            // mutableObject.setFoo((cast)fooDelegate.getValue());
-            sw.println("%s.%s((%s)%s.getObject());", mutableObjectExpression,
-                d.getSetterName(), d.getEditedType().getQualifiedSourceName(),
-                delegateFields.get(d));
-          } else {
-            // mutableObject.setFoo(editor.subEditor.getValue());
-            sw.println("%s.%s(editor.%s.getValue());", mutableObjectExpression,
-                d.getSetterName(), d.getSimpleExpression());
-          }
-          sw.outdent();
-          sw.println("}");
-        } else if (d.isDelegateRequired()) {
-          // if (fooDelegate != null && can().reach().without().npe()) {
-          sw.println("if (%s != null && %s) {", delegateFields.get(d),
-              d.getBeanOwnerGuard("getObject()"));
-          sw.indent();
-          // fooDelegate.flush(errorAccumulator);
-          sw.println("%s.flush(errorAccumulator);", delegateFields.get(d));
-
-          sw.outdent();
-          sw.println("}");
-        }
-      }
-      sw.outdent();
-      sw.println("}");
-
-      // Flush each sub-delegate
-      sw.println("protected void flushSubEditorErrors(%s errorAccumulator) {",
-          List.class.getCanonicalName());
-      sw.indent();
-      for (EditorData d : data) {
-        if (d.isDelegateRequired()) {
-          // if (fooDelegate != null) fooDelegate.flushErrors(accumulator);
-          sw.println("if (%1$s != null) %1$s.flushErrors(errorAccumulator);",
-              delegateFields.get(d));
-        }
-      }
-      sw.outdent();
-      sw.println("}");
-
-      sw.println("protected boolean hasSubEditorsWithoutDelegates() {");
-      boolean hasSubEditorsWithoutDelegates = false;
-      for (EditorData d : data) {
-        if (!d.isDelegateRequired()) {
-          hasSubEditorsWithoutDelegates = true;
-          break;
-        }
-      }
-      sw.indentln("return %s;", hasSubEditorsWithoutDelegates ? "true"
-          : "false");
-      sw.println("}");
-
-      // isDirty() traversal method for sub-editors without delegates
-      sw.println("protected boolean isDirtyCheckLeaves() {");
-      sw.indent();
-      if (hasSubEditorsWithoutDelegates) {
-        for (EditorData d : data) {
-          if (!d.isDelegateRequired()) {
-            // if (editor.subEditor != null &&
-            sw.println("if (editor.%s != null &&", d.getSimpleExpression());
-            // !equals(editor.sub.getValue(), lastLeafValues.get("foo.bar"))) {
-            sw.indentln(
-                "!equals(editor.%s.getValue(), lastLeafValues.get(\"%s\"))) {",
-                d.getSimpleExpression(), d.getDeclaredPath());
-            sw.indentln("return true;");
-            sw.println("}");
-          }
-        }
-      }
-      sw.println("return false;");
-      sw.outdent();
-      sw.println("}");
-
-      // Reset the data being displayed
-      sw.println("protected void refreshEditors() {",
-          DelegateMap.class.getCanonicalName());
-      sw.indent();
-      for (EditorData d : data) {
-        if (d.isDelegateRequired()) {
-          // if (subEditorDelegate != null) {
-          sw.println("if (%s != null) {", delegateFields.get(d));
-          sw.indent();
-          // if (can().access().without().npe()) {
-          sw.println("if (%s) {", d.getBeanOwnerGuard("getObject()"));
-          // subDelegate.refresh(getObject().getFoo().getBar());
-          sw.indentln("%s.refresh(getObject()%s%s);", delegateFields.get(d),
-              d.getBeanOwnerExpression(), d.getGetterExpression());
-          // } else { subDelegate.refresh(null); }
-          sw.println("} else { %s.refresh(null); }", delegateFields.get(d));
-          sw.outdent();
-          sw.println("}");
-        } else if (d.isLeafValueEditor()) {
-          // if (editor.subEditor != null) {
-          sw.println("if (editor.%s != null) {", d.getSimpleExpression());
-          sw.indent();
-          // if (can().access().without().npe()) {
-          sw.println("if (%s) {", d.getBeanOwnerGuard("getObject()"));
-          sw.indent();
-          // Bar value = getObject()....;
-          sw.println("%s value = getObject()%s%s;",
-              d.getEditedType().getQualifiedSourceName(),
-              d.getBeanOwnerExpression(), d.getGetterExpression());
-          // editor.subEditor.setValue(value);
-          sw.println("editor.%s.setValue(value);", d.getSimpleExpression());
-          // lastLeafValues.put("foo.bar", value);
-          sw.println("lastLeafValues.put(\"%s\", value);", d.getDeclaredPath());
-          sw.outdent();
-          sw.println("} else {");
-          sw.indent();
-          sw.println("editor.%s.setValue(null);", d.getSimpleExpression());
-          sw.println("lastLeafValues.put(\"%s\", null);", d.getDeclaredPath());
-          sw.outdent();
-          sw.println("}");
-          sw.outdent();
-          sw.println("}");
-        }
-      }
-      sw.outdent();
-      sw.println("}");
-
-      // Write instance delegate to static implementation
-      sw.println("protected void traverse(%s paths) {",
-          List.class.getCanonicalName());
-      sw.indentln("traverseEditor(getEditor(), \"\", paths);");
-      sw.println("}");
-
-      sw.println("public static void traverseEditor(%s editor,"
-          + " String prefix, %s<String> paths) {",
-          editor.getQualifiedSourceName(), List.class.getName());
-      sw.indent();
-      for (EditorData d : data) {
-        if (d.isDelegateRequired() || d.isDeclaredPathNested()
-            || d.isCompositeEditor()) {
-          // if (editor.subEditor != null) {
-          sw.println("if (editor.%s != null) {", d.getSimpleExpression());
-          sw.indent();
-          // String localPath = appendPath(prefix, "somePath");
-          sw.println("String localPath = appendPath(prefix, \"%s\");",
-              d.getDeclaredPath());
-          sw.println("paths.add(localPath);");
-
-          if (d.isDelegateRequired()) {
-            // fooDelegate.traverseEditor(editor.subEditor, localPath, paths);
-            sw.println("%s.traverseEditor(editor.%s, localPath, paths);",
-                getEditorDelegate(d), d.getSimpleExpression());
-          }
-          if (d.isCompositeEditor()) {
-            /*
-             * composedDelegate.traverseEditor(editor.subEditor.
-             * createEditorForTraversal(), localPath, paths);
-             */
-            sw.println(
-                "%s.traverseEditor(editor.%s.createEditorForTraversal(), localPath, paths);",
-                getEditorDelegate(d.getComposedData()), d.getSimpleExpression());
-          }
-          sw.outdent();
-          sw.println("}");
-        }
-      }
-      sw.outdent();
-      sw.println("}");
-
       sw.commit(logger);
     }
     return packageName + "." + delegateSimpleName;
@@ -388,31 +221,107 @@
   protected abstract String mutableObjectExpression(EditorData data,
       String sourceObjectExpression);
 
-  /**
-   * @param logger
-   * @param context
-   * @param model
-   * @param sw
-   * 
-   * @throws UnableToCompleteException
-   */
   protected void writeAdditionalContent(TreeLogger logger,
       GeneratorContext context, EditorModel model, SourceWriter sw)
       throws UnableToCompleteException {
   }
 
-  protected abstract void writeDelegateInitialization(SourceWriter sw,
-      EditorData d, Map<EditorData, String> delegateFields);
-
   private String escapedBinaryName(String binaryName) {
     return binaryName.replace("_", "_1").replace('$', '_').replace('.', '_');
   }
 
+  /**
+   * Create an EditorContext implementation that will provide acess to
+   * {@link data} owned by {@link parent}. In other words, given the EditorData
+   * for a {@code PersonEditor} and the EditorData for a {@code AddressEditor}
+   * nested in the {@code PersonEditor}, create an EditorContext that will
+   * describe the relationship.
+   * 
+   * @return the qualified name of the EditorContext implementation
+   */
+  private String getEditorContext(EditorData parent, EditorData data) {
+    String pkg = parent.getEditorType().getPackage().getName();
+    // PersonEditor_manager_name_Context
+    String simpleName = escapedBinaryName(parent.getEditorType().getName())
+        + "_" + data.getDeclaredPath().replace("_", "_1").replace(".", "_")
+        + "_Context";
+
+    PrintWriter pw = context.tryCreate(logger, pkg, simpleName);
+    if (pw != null) {
+      ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
+          pkg, simpleName);
+      String editedSourceName = data.getEditedType().getQualifiedSourceName();
+      f.setSuperclass(AbstractEditorContext.class.getCanonicalName() + "<"
+          + editedSourceName + ">");
+      SourceWriter sw = f.createSourceWriter(context, pw);
+
+      String parentSourceName = parent.getEditedType().getQualifiedSourceName();
+      sw.println("private final %s parent;", parentSourceName);
+      sw.println("public %s(%s parent, %s<%s, ?> delegate) {", simpleName,
+          parentSourceName, AbstractEditorDelegate.class.getCanonicalName(),
+          editedSourceName);
+      sw.indentln("super(delegate);");
+      sw.indentln("this.parent = parent;");
+      sw.println("}");
+
+      sw.println("public %s(%s parent, %s<%s> editor, String path) {",
+          simpleName, parentSourceName, Editor.class.getCanonicalName(),
+          editedSourceName);
+      sw.indentln("super(editor,path);");
+      sw.indentln("this.parent = parent;");
+      sw.println("}");
+
+      sw.println("@Override public boolean canSetInModel() {");
+      sw.indentln("return parent != null && %s && %s;",
+          data.getSetterName() == null ? "false" : "true",
+          data.getBeanOwnerGuard("parent"));
+      sw.println("}");
+
+      sw.println("@Override public %s checkAssignment(Object value) {",
+          editedSourceName);
+      sw.indentln("return (%s) value;", editedSourceName);
+      sw.println("}");
+
+      sw.println(
+          "@Override public Class<%1$s> getEditedType() { return %1$s.class; }",
+          editedSourceName);
+
+      sw.println("@Override public %s getFromModel() {", editedSourceName);
+      sw.indentln("return (parent != null && %s) ? parent%s%s : null;",
+          data.getBeanOwnerGuard("parent"), data.getBeanOwnerExpression(),
+          data.getGetterExpression());
+      sw.println("}");
+
+      sw.println("@Override public void setInModel(%s data) {",
+          editedSourceName);
+      if (data.getSetterName() == null) {
+        sw.indentln("throw new UnsupportedOperationException();");
+      } else {
+        sw.indentln("parent%s.%s(data);", data.getBeanOwnerExpression(),
+            data.getSetterName());
+      }
+      sw.println("}");
+
+      sw.commit(logger);
+    }
+    return pkg + "." + simpleName;
+  }
+
   private void writeCreateDelegate(SourceWriter sw)
       throws UnableToCompleteException {
     String editorDelegateName = getEditorDelegate(model.getRootData());
 
-    sw.println("protected %s createDelegate() {",
+    sw.println("@Override public void accept(%s visitor) {",
+        EditorVisitor.class.getCanonicalName());
+    sw.indent();
+    sw.println("%1$s ctx = new %1$s(getDelegate(), %2$s.class, getObject());",
+        RootEditorContext.class.getCanonicalName(),
+        model.getProxyType().getQualifiedSourceName());
+    sw.println("ctx.traverse(visitor, getDelegate());");
+    sw.outdent();
+    sw.println("}");
+
+    sw.println("@Override protected %s createDelegate() {",
         Name.getSourceNameForClass(getEditorDelegateType()),
         model.getProxyType().getQualifiedSourceName(),
         model.getEditorType().getQualifiedSourceName());
diff --git a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
index 33465d9..53f1a5e 100644
--- a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
@@ -19,9 +19,6 @@
 import com.google.gwt.editor.client.impl.AbstractSimpleBeanEditorDriver;
 import com.google.gwt.editor.client.impl.SimpleBeanEditorDelegate;
 import com.google.gwt.editor.rebind.model.EditorData;
-import com.google.gwt.user.rebind.SourceWriter;
-
-import java.util.Map;
 
 /**
  * Generates implementations of {@link SimpleBeanEditorDriver}.
@@ -49,15 +46,4 @@
       String sourceObjectExpression) {
     return sourceObjectExpression;
   }
-
-  @Override
-  protected void writeDelegateInitialization(SourceWriter sw, EditorData d,
-      Map<EditorData, String> delegateFields) {
-    // fooDelegate.initialize(appendPath("foo"), getObject().getFoo(),
-    // editor.fooEditor);
-    sw.println("%s.initialize(appendPath(\"%s\"), getObject()%s%s,"
-        + " editor.%s, delegateMap);", delegateFields.get(d),
-        d.getPropertyName(), d.getBeanOwnerExpression(), d.getGetterExpression(),
-        d.getSimpleExpression());
-  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java b/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
new file mode 100644
index 0000000..0482875
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.requestfactory.client;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.requestfactory.shared.RequestContext;
+
+/**
+ * Editors used with {@link RequestFactoryEditorDriver} that implement this
+ * interface will be provided with the {@link RequestContext} associated with
+ * the current editing session.
+ * 
+ * @param <T> the type of data being edited
+ */
+public interface HasRequestContext<T> extends Editor<T> {
+  /**
+   * Called by {@link RequestFactoryEditorDriver} with the
+   * {@link RequestContext} passed into
+   * {@link RequestFactoryEditorDriver#edit(Object, RequestContext) edit()} or
+   * {@code null} if {@link RequestFactoryEditorDriver#display(Object)
+   * display()} is called.
+   * 
+   * @param the RequestContext associated with the current editing session which
+   *          may be {@code null}
+   */
+  void setRequestContext(RequestContext ctx);
+}
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
index 9d0ee99..71cb681 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
@@ -16,20 +16,16 @@
 package com.google.gwt.requestfactory.client;
 
 import com.google.gwt.editor.client.Editor;
-import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorDriver;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Violation;
 
-import java.util.List;
-
-import javax.validation.ConstraintViolation;
-
 /**
  * The interface that links RequestFactory and the Editor framework together.
- * Used for configuration and lifecycle control. Expected that this will be
- * created with
+ * <p>
+ * Instances of this interface are created with
  * 
  * <pre>
  * interface MyRFED extends RequestFactoryEditorDriver&lt;MyObjectProxy, MyObjectEditor> {}
@@ -45,13 +41,14 @@
  * }
  * </pre>
  * 
- * <p>
- * 
  * @param <P> the type of Proxy being edited
  * @param <E> the type of Editor that will edit the Record
- * @see {@link com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver}
+ * @see HasRequestContext
+ * @see {@link com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver
+ *      MockRequestFactoryEditorDriver}
  */
-public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>> {
+public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>>
+    extends EditorDriver<RequestContext> {
   /**
    * Start driving the Editor and its sub-editors with data for display-only
    * mode.
@@ -73,25 +70,16 @@
   void edit(P proxy, RequestContext request);
 
   /**
-   * Ensures that the Editor passed into {@link #initialize} and its
-   * sub-editors, if any, have synced their UI state by invoking flushing them
-   * in a depth-first manner.
+   * Update the object being edited with the current state of the Editor.
    * 
-   * @return the RequestContext passed into {@link #edit}
+   * @return the RequestContext passed into
+   *         {@link #edit(Object, RequestContext)}
    * @throws IllegalStateException if {@link #edit(Object, RequestContext)} has
-   *           not been called with a non-null {@link RequestContext}
+   *           not been called
    */
   RequestContext flush();
 
   /**
-   * Returns any unconsumed {@link EditorError EditorErrors} from the last call
-   * to {@link #flush()}.
-   * 
-   * @return a List of {@link EditorError} instances
-   */
-  List<EditorError> getErrors();
-
-  /**
    * Returns a new array containing the request paths.
    * 
    * @return an array of Strings
@@ -99,13 +87,6 @@
   String[] getPaths();
 
   /**
-   * Indicates if the last call to {@link #flush()} resulted in any errors.
-   * 
-   * @return {@code} true if errors are present
-   */
-  boolean hasErrors();
-
-  /**
    * Overload of {@link #initialize(RequestFactory, Editor)} to allow a modified
    * {@link EventBus} to be monitored for subscription services.
    * 
@@ -139,31 +120,11 @@
   void initialize(E editor);
 
   /**
-   * Returns {@code true} if any of the Editors in the hierarchy have been
-   * modified relative to the last value passed into {@link #edit(Object)}.
-   * 
-   * @see com.google.gwt.editor.client.EditorDelegate#setDirty(boolean)
-   */
-  boolean isDirty();
-
-  /**
-   * Show {@link ConstraintViolation ConstraintViolations} generated through a
-   * JSR 303 Validator. The violations will be converted into
-   * {@link EditorError} objects whose {@link EditorError#getUserData()
-   * getUserData()} method can be used to access the original
-   * ConstraintViolation object.
-   * 
-   * @param violations an Iterable over {@link ConstraintViolation} instances
-   * @return <code>true</code> if there were any unconsumed EditorErrors which
-   *         can be retrieved from {@link #getErrors()}
-   */
-  boolean setConstraintViolations(Iterable<ConstraintViolation<?>> violations);
-
-  /**
    * Show Violations returned from an attempt to submit a request. The
-   * violations will be converted into {@link EditorError} objects whose
-   * {@link EditorError#getUserData() getUserData()} method can be used to
-   * access the original Violation object.
+   * violations will be converted into
+   * {@link com.google.gwt.editor.client.EditorError EditorError} objects whose
+   * {@link com.google.gwt.editor.client.EditorError#getUserData()
+   * getUserData()} method can be used to access the original Violation object.
    * 
    * @param violations an Iterable over {@link Violation} instances
    * @return <code>true</code> if there were any unconsumed EditorErrors which
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index 672c2c8..d60cf8c 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -15,33 +15,37 @@
  */
 package com.google.gwt.requestfactory.client.impl;
 
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.editor.client.Editor;
-import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
 import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.BaseEditorDriver;
 import com.google.gwt.editor.client.impl.DelegateMap;
+import com.google.gwt.editor.client.impl.DelegateMap.KeyMethod;
 import com.google.gwt.editor.client.impl.SimpleViolation;
 import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.requestfactory.client.HasRequestContext;
 import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.ValueProxy;
 import com.google.gwt.requestfactory.shared.Violation;
+import com.google.gwt.requestfactory.shared.impl.Constants;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
-import javax.validation.ConstraintViolation;
-
 /**
  * Contains utility methods for top-level driver implementations.
  * 
- * @param <R> the type of Record
+ * @param <R> the type being edited
  * @param <E> the type of Editor
  */
 public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
-    implements RequestFactoryEditorDriver<R, E> {
+    extends BaseEditorDriver<R, E> implements RequestFactoryEditorDriver<R, E> {
 
   /**
    * Adapts a RequestFactory Violation object to the SimpleViolation interface.
@@ -72,7 +76,6 @@
       return v;
     }
   }
-
   /**
    * Provides a source of SimpleViolation objects based on RequestFactory's
    * simplified Violation interface.
@@ -126,59 +129,60 @@
   }
 
   private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
-    public Object key(final Object object) {
+    public Object key(Object object) {
       if (object instanceof EntityProxy) {
         return ((EntityProxy) object).stableId();
       } else if (object instanceof ValueProxy) {
+        AutoBean<?> bean = AutoBeanUtils.getAutoBean(object);
+        // Possibly replace an editable ValueProxy with its immutable base
+        AutoBean<?> parent = bean.getTag(Constants.PARENT_OBJECT);
+        if (parent != null) {
+          object = parent.as();
+        }
         return new ValueProxyHolder((ValueProxy) object);
       }
       return null;
     }
   };
 
-  private RequestFactoryEditorDelegate<R, E> delegate;
-  private DelegateMap delegateMap = new DelegateMap(PROXY_ID_KEY);
-  private E editor;
   private EventBus eventBus;
-  private List<EditorError> errors;
-  private List<String> paths = new ArrayList<String>();
-  private RequestFactory requestFactory;
+  private List<String> paths;
+  private RequestFactory factory;
   private RequestContext saveRequest;
 
   public void display(R object) {
     edit(object, null);
   }
 
-  public void edit(R object, RequestContext saveRequest) {
-    checkEditor();
+  public void edit(R object, final RequestContext saveRequest) {
     this.saveRequest = saveRequest;
-    delegate = createDelegate();
-    delegate.initialize(eventBus, requestFactory, "", object, editor,
-        delegateMap, saveRequest);
-    delegateMap.put(object, delegate);
+    // Provide the delegate and maybe the editor with the RequestContext
+    accept(new EditorVisitor() {
+      @Override
+      public <T> void endVisit(EditorContext<T> ctx) {
+        RequestFactoryEditorDelegate<?, ?> delegate = (RequestFactoryEditorDelegate<?, ?>) ctx.getEditorDelegate();
+        if (delegate != null) {
+          delegate.setRequestContext(saveRequest);
+        }
+        Editor<T> editor = ctx.getEditor();
+        if (editor instanceof HasRequestContext) {
+          ((HasRequestContext<T>) editor).setRequestContext(saveRequest);
+        }
+      }
+    });
+    doEdit(object);
   }
 
   public RequestContext flush() {
-    checkDelegate();
     checkSaveRequest();
-    errors = new ArrayList<EditorError>();
-    delegate.flush(errors);
-
+    doFlush();
     return saveRequest;
   }
 
-  public List<EditorError> getErrors() {
-    return errors;
-  }
-
   public String[] getPaths() {
     return paths.toArray(new String[paths.size()]);
   }
 
-  public boolean hasErrors() {
-    return !errors.isEmpty();
-  }
-
   public void initialize(E editor) {
     doInitialize(null, null, editor);
   }
@@ -194,66 +198,34 @@
     initialize(requestFactory.getEventBus(), requestFactory, editor);
   }
 
-  public boolean isDirty() {
-    for (AbstractEditorDelegate<?, ?> d : delegateMap) {
-      if (d.isDirty()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public boolean setConstraintViolations(
-      Iterable<ConstraintViolation<?>> violations) {
-    return doSetViolations(SimpleViolation.iterableFromConstrantViolations(violations));
-  }
-
   public boolean setViolations(Iterable<Violation> violations) {
     return doSetViolations(new ViolationIterable(violations));
   }
 
-  protected abstract RequestFactoryEditorDelegate<R, E> createDelegate();
-
-  protected E getEditor() {
-    return editor;
-  }
-
-  protected abstract void traverseEditors(List<String> paths);
-
-  private void checkDelegate() {
-    if (delegate == null) {
-      throw new IllegalStateException("Must call edit() first");
-    }
-  }
-
-  private void checkEditor() {
-    if (editor == null) {
-      throw new IllegalStateException("Must call initialize() first");
-    }
-  }
-
-  private void checkSaveRequest() {
+  protected void checkSaveRequest() {
     if (saveRequest == null) {
       throw new IllegalStateException("edit() was called with a null Request");
     }
   }
 
-  private void doInitialize(EventBus eventBus, RequestFactory requestFactory,
-      E editor) {
-    this.eventBus = eventBus;
-    this.requestFactory = requestFactory;
-    this.editor = editor;
-
-    traverseEditors(paths);
+  @Override
+  protected void configureDelegate(AbstractEditorDelegate<R, E> rootDelegate) {
+    ((RequestFactoryEditorDelegate<R, E>) rootDelegate).initialize(eventBus,
+        factory, "", getEditor());
   }
 
-  private boolean doSetViolations(Iterable<SimpleViolation> violations) {
-    checkDelegate();
-    SimpleViolation.pushViolations(violations, delegateMap);
+  protected void doInitialize(EventBus eventBus, RequestFactory requestFactory,
+      E editor) {
+    this.eventBus = eventBus;
+    this.factory = requestFactory;
+    super.doInitialize(editor);
+    PathCollector c = new PathCollector();
+    accept(c);
+    this.paths = c.getPaths();
+  }
 
-    // Flush the errors, which will take care of co-editor chains.
-    errors = new ArrayList<EditorError>();
-    delegate.flushErrors(errors);
-    return hasErrors();
+  @Override
+  protected KeyMethod getViolationKeyMethod() {
+    return PROXY_ID_KEY;
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java b/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
new file mode 100644
index 0000000..e654e41
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.requestfactory.client.impl;
+
+import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Collects all non-value-type paths in an editor hierarchy for use with
+ * {@link com.google.gwt.requestfactory.client.RequestFactoryEditorDriver#getPaths()}
+ * .
+ */
+class PathCollector extends EditorVisitor {
+  /**
+   * Use a set in the case of aliased editors, so we don't repeat path entries.
+   */
+  private final Set<String> paths = new LinkedHashSet<String>();
+
+  public List<String> getPaths() {
+    return new ArrayList<String>(paths);
+  }
+
+  public <T> boolean visit(EditorContext<T> ctx) {
+    String path = ctx.getAbsolutePath();
+    if (path.length() > 0 && !ValueCodex.canDecode(ctx.getEditedType())) {
+      paths.add(path);
+    }
+    if (ctx.asCompositeEditor() != null) {
+      ctx.traverseSyntheticCompositeEditor(this);
+    }
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
index 55b1ae3..bc40e1b 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
@@ -17,7 +17,7 @@
 
 import com.google.gwt.editor.client.Editor;
 import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
-import com.google.gwt.editor.client.impl.DelegateMap;
+import com.google.gwt.editor.client.impl.Refresher;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.requestfactory.shared.BaseProxy;
@@ -30,7 +30,6 @@
 import com.google.gwt.requestfactory.shared.WriteOperation;
 import com.google.gwt.requestfactory.shared.impl.AbstractRequestContext;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -42,17 +41,16 @@
  */
 public abstract class RequestFactoryEditorDelegate<P, E extends Editor<P>>
     extends AbstractEditorDelegate<P, E> {
-
   private class SubscriptionHandler implements
       EntityProxyChange.Handler<EntityProxy> {
 
     public void onProxyChange(EntityProxyChange<EntityProxy> event) {
       if (event.getWriteOperation().equals(WriteOperation.UPDATE)
           && event.getProxyId().equals(((EntityProxy) getObject()).stableId())) {
-        List<String> paths = new ArrayList<String>();
-        traverse(paths);
+        PathCollector collector = new PathCollector();
+        accept(collector);
         EntityProxyId<?> id = event.getProxyId();
-        doFind(paths, id);
+        doFind(collector.getPaths(), id);
       }
     }
 
@@ -68,7 +66,8 @@
     public void onSuccess(EntityProxy response) {
       @SuppressWarnings("unchecked")
       P cast = (P) response;
-      refresh(cast);
+      setObject(cast);
+      accept(new Refresher());
     }
   }
 
@@ -76,13 +75,8 @@
   protected RequestFactory factory;
   protected RequestContext request;
 
-  public void initialize(EventBus eventBus, RequestFactory factory,
-      String pathSoFar, P object, E editor, DelegateMap delegateMap,
-      RequestContext editRequest) {
-    this.eventBus = eventBus;
-    this.factory = factory;
-    this.request = editRequest;
-    super.initialize(pathSoFar, object, editor, delegateMap);
+  public void setRequestContext(RequestContext request) {
+    this.request = request;
   }
 
   @Override
@@ -114,6 +108,13 @@
   }
 
   @Override
+  protected <R, S extends Editor<R>> void addSubDelegate(
+      AbstractEditorDelegate<R, S> subDelegate, String path, S subEditor) {
+    RequestFactoryEditorDelegate<R, S> d = (RequestFactoryEditorDelegate<R, S>) subDelegate;
+    d.initialize(eventBus, factory, path, subEditor);
+  }
+
+  @Override
   protected <T> T ensureMutable(T object) {
     if (request == null) {
       // Read-only mode
@@ -127,12 +128,19 @@
     return object;
   }
 
+  protected void initialize(EventBus eventBus, RequestFactory factory,
+      String pathSoFar, E editor) {
+    this.eventBus = eventBus;
+    this.factory = factory;
+    super.initialize(pathSoFar, editor);
+  }
+
+  /**
+   * Must call four-arg version instead.
+   */
   @Override
-  protected <R, S extends Editor<R>> void initializeSubDelegate(
-      AbstractEditorDelegate<R, S> subDelegate, String path, R object,
-      S subEditor, DelegateMap delegateMap) {
-    ((RequestFactoryEditorDelegate<R, S>) subDelegate).initialize(eventBus,
-        factory, path, object, subEditor, delegateMap, request);
+  protected void initialize(String pathSoFar, E editor) {
+    throw new UnsupportedOperationException();
   }
 
   @Override
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
index f870386..f0e4d89 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.editor.client.Editor;
 import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorVisitor;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.shared.RequestContext;
@@ -46,6 +47,12 @@
   private RequestFactory requestFactory;
 
   /**
+   * A no-op method.
+   */
+  public void accept(EditorVisitor visitor) {
+  }
+
+  /**
    * Records its arguments.
    */
   public void display(P proxy) {
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
index 1d309b7..9926e0d 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
@@ -21,15 +21,10 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.editor.rebind.AbstractEditorDriverGenerator;
 import com.google.gwt.editor.rebind.model.EditorData;
-import com.google.gwt.editor.rebind.model.EditorModel;
 import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.client.impl.AbstractRequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.client.impl.RequestFactoryEditorDelegate;
 import com.google.gwt.requestfactory.shared.BaseProxy;
-import com.google.gwt.user.rebind.SourceWriter;
-
-import java.util.List;
-import java.util.Map;
 
 /**
  * Generates implementations of RFEDs.
@@ -73,25 +68,4 @@
       return sourceObjectExpression;
     }
   }
-
-  @Override
-  protected void writeAdditionalContent(TreeLogger logger,
-      GeneratorContext context, EditorModel model, SourceWriter sw)
-      throws UnableToCompleteException {
-    sw.println("protected void traverseEditors(%s<String> paths) {",
-        List.class.getName());
-    sw.indentln("%s.traverseEditor(getEditor(), \"\", paths);",
-        getEditorDelegate(model.getRootData()));
-    sw.println("}");
-  }
-
-  @Override
-  protected void writeDelegateInitialization(SourceWriter sw, EditorData d,
-      Map<EditorData, String> delegateFields) {
-    sw.println("%s.initialize(eventBus, factory, "
-        + "appendPath(\"%s\"), getObject()%s%s," + " editor.%s,"
-        + " delegateMap, request);", delegateFields.get(d),
-        d.getPropertyName(), d.getBeanOwnerExpression(),
-        d.getGetterExpression(), d.getSimpleExpression());
-  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 4f7e01d..ea8dff2 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -87,7 +87,7 @@
       currentProxy = (BaseProxy) edited.as();
 
       // Try to find the original, immutable version.
-      AutoBean<BaseProxy> parentBean = edited.getTag(PARENT_OBJECT);
+      AutoBean<BaseProxy> parentBean = edited.getTag(Constants.PARENT_OBJECT);
       parentProxy = parentBean == null ? null : parentBean.as();
       path = message.getPath();
       this.message = message.getMessage();
@@ -114,11 +114,11 @@
     }
   }
 
-  private static final String PARENT_OBJECT = "parentObject";
-  private static final WriteOperation[] PERSIST_AND_UPDATE = {
-      WriteOperation.PERSIST, WriteOperation.UPDATE};
   private static final WriteOperation[] DELETE_ONLY = {WriteOperation.DELETE};
+  private static final WriteOperation[] PERSIST_AND_UPDATE = {
+    WriteOperation.PERSIST, WriteOperation.UPDATE};
   private static final WriteOperation[] UPDATE_ONLY = {WriteOperation.UPDATE};
+
   private final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
   private boolean locked;
   private final AbstractRequestFactory requestFactory;
@@ -182,7 +182,7 @@
     // Create editable copies
     AutoBean<T> parent = bean;
     bean = cloneBeanAndCollections(bean);
-    bean.setTag(PARENT_OBJECT, parent);
+    bean.setTag(Constants.PARENT_OBJECT, parent);
     return bean.as();
   }
 
@@ -262,7 +262,7 @@
      * the JavaDoc.
      */
     for (AutoBean<?> bean : editedProxies.values()) {
-      AutoBean<?> previous = bean.getTag(PARENT_OBJECT);
+      AutoBean<?> previous = bean.getTag(Constants.PARENT_OBJECT);
       if (previous == null) {
         // Compare to empty object
         Class<?> proxyClass = ((EntityProxy) bean.as()).stableId().getProxyClass();
@@ -364,7 +364,7 @@
       operation.setSyntheticId(stableId.getSyntheticId());
       operation.setStrength(Strength.SYNTHETIC);
     } else {
-      parent = proxyBean.getTag(PARENT_OBJECT);
+      parent = proxyBean.getTag(Constants.PARENT_OBJECT);
       // Requests involving existing objects use the persisted id
       operation.setServerId(stableId.getServerId());
       operation.setOperation(WriteOperation.UPDATE);
@@ -765,7 +765,7 @@
    */
   private void makeImmutable(final AutoBean<? extends BaseProxy> toMutate) {
     // Always diff'ed against itself, producing a no-op
-    toMutate.setTag(PARENT_OBJECT, toMutate);
+    toMutate.setTag(Constants.PARENT_OBJECT, toMutate);
     // Act with entity-identity semantics
     toMutate.setTag(REQUEST_CONTEXT, null);
     toMutate.setFrozen(true);
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
index 5524e05..ec0cba6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -20,8 +20,9 @@
  */
 public interface Constants {
   String DOMAIN_OBJECT = "domainObject";
-  String VERSION_PROPERTY_B64 = "version";
   String IN_RESPONSE = "inResponse";
+  String PARENT_OBJECT = "parentObject";
   String REQUEST_CONTEXT = "requestContext";
   String STABLE_ID = "stableId";
+  String VERSION_PROPERTY_B64 = "version";
 }
diff --git a/user/test/com/google/gwt/editor/EditorSuite.java b/user/test/com/google/gwt/editor/EditorSuite.java
index a4d615c..e0abf25 100644
--- a/user/test/com/google/gwt/editor/EditorSuite.java
+++ b/user/test/com/google/gwt/editor/EditorSuite.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.editor;
 
+import com.google.gwt.editor.client.DirtyEditorTest;
 import com.google.gwt.editor.client.EditorErrorTest;
 import com.google.gwt.editor.client.SimpleBeanEditorTest;
 import com.google.gwt.editor.client.adapters.ListEditorWrapperTest;
@@ -33,6 +34,7 @@
     GWTTestSuite suite = new GWTTestSuite(
         "Test suite for core Editor functions");
     suite.addTestSuite(DelegateMapTest.class);
+    suite.addTestSuite(DirtyEditorTest.class);
     suite.addTestSuite(EditorModelTest.class);
     suite.addTestSuite(EditorErrorTest.class);
     suite.addTestSuite(ListEditorWrapperTest.class);
diff --git a/user/test/com/google/gwt/editor/client/DirtyEditorTest.java b/user/test/com/google/gwt/editor/client/DirtyEditorTest.java
new file mode 100644
index 0000000..a17a65c
--- /dev/null
+++ b/user/test/com/google/gwt/editor/client/DirtyEditorTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2011 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.editor.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.editor.client.SimpleBeanEditorTest.PersonEditorWithDelegate;
+import com.google.gwt.editor.client.SimpleBeanEditorTest.PersonEditorWithDelegateDriver;
+import com.google.gwt.editor.client.SimpleBeanEditorTest.PersonEditorWithOptionalAddressDriver;
+import com.google.gwt.editor.client.SimpleBeanEditorTest.PersonEditorWithOptionalAddressEditor;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.editor.client.adapters.OptionalFieldEditor;
+import com.google.gwt.editor.client.adapters.SimpleEditor;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 
+ */
+public class DirtyEditorTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.editor.Editor";
+  }
+
+  public void testDirty() {
+    PersonEditor editor = new PersonEditor();
+    PersonEditorDriver driver = GWT.create(PersonEditorDriver.class);
+    driver.initialize(editor);
+    driver.edit(person);
+
+    // Freshly-initialized should not be dirty
+    assertFalse(driver.isDirty());
+
+    // Changing the Person object should not affect the dirty status
+    person.setName("blah");
+    assertFalse(driver.isDirty());
+
+    editor.addressEditor.city.setValue("Foo");
+    assertTrue(driver.isDirty());
+
+    // Check that flushing doesn't clear the dirty state
+    driver.flush();
+    assertTrue(driver.isDirty());
+
+    // Reset to original value
+    editor.addressEditor.city.setValue("City");
+    assertFalse(driver.isDirty());
+
+    // Try a null value
+    editor.managerName.setValue(null);
+    assertTrue(driver.isDirty());
+  }
+
+  public void testDirtyWithDelegate() {
+    PersonEditorWithDelegate editor = new PersonEditorWithDelegate();
+    PersonEditorWithDelegateDriver driver = GWT.create(PersonEditorWithDelegateDriver.class);
+    driver.initialize(editor);
+    driver.edit(person);
+
+    // Freshly-initialized should not be dirty
+    assertFalse(driver.isDirty());
+
+    // Use the delegate to toggle the state
+    editor.delegate.setDirty(true);
+    assertTrue(driver.isDirty());
+
+    // Use the delegate to clear the state
+    editor.delegate.setDirty(false);
+    assertFalse(driver.isDirty());
+
+    // Check that the delegate has no influence over values
+    editor.addressEditor.city.setValue("edited");
+    assertTrue(driver.isDirty());
+    editor.delegate.setDirty(false);
+    assertTrue(driver.isDirty());
+    editor.delegate.setDirty(true);
+    assertTrue(driver.isDirty());
+  }
+
+  public void testDirtyWithOptionalEditor() {
+    person.address = null;
+
+    AddressEditor addressEditor = new AddressEditor();
+    PersonEditorWithOptionalAddressEditor editor = new PersonEditorWithOptionalAddressEditor(
+        addressEditor);
+    PersonEditorWithOptionalAddressDriver driver = GWT.create(PersonEditorWithOptionalAddressDriver.class);
+    driver.initialize(editor);
+    driver.edit(person);
+
+    // Freshly-initialized should not be dirty
+    assertFalse(driver.isDirty());
+
+    // Change the instance being edited
+    Address a = new Address();
+    editor.address.setValue(a);
+    assertTrue(driver.isDirty());
+
+    // Check restoration works
+    editor.address.setValue(null);
+    assertFalse(driver.isDirty());
+  }
+
+  public void testEditResetsDirty() {
+    PersonEditorWithDelegate editor = new PersonEditorWithDelegate();
+    PersonEditorWithDelegateDriver driver = GWT.create(PersonEditorWithDelegateDriver.class);
+    driver.initialize(editor);
+    driver.edit(person);
+
+    // Freshly-initialized should not be dirty
+    assertFalse(driver.isDirty());
+
+    editor.addressEditor.city.setValue("blah");
+    assertTrue(driver.isDirty());
+
+    driver.edit(person);
+    assertFalse(driver.isDirty());
+
+    editor.delegate.setDirty(true);
+    assertTrue(driver.isDirty());
+    driver.edit(person);
+    assertFalse(driver.isDirty());
+  }
+
+  public void testEditResetsDirtyReplacement() {
+    Person person2 = new Person();
+    person2.setName("Pod");
+
+    PersonEditorWithDelegate editor = new PersonEditorWithDelegate();
+    PersonEditorWithDelegateDriver driver = GWT.create(PersonEditorWithDelegateDriver.class);
+    driver.initialize(editor);
+    driver.edit(person);
+
+    editor.addressEditor.street.setValue("blah");
+    assertTrue(driver.isDirty());
+
+    driver.edit(person2);
+    assertFalse(driver.isDirty());
+  }
+
+  class Workgroup {
+    private String label;
+
+    String getLabel() {
+      return label;
+    }
+
+    void setLabel(String label) {
+      this.label = label;
+    }
+
+    private List<Person> people;
+
+    List<Person> getPeople() {
+      return people;
+    }
+
+    void setPeople(List<Person> people) {
+      this.people = people;
+    }
+  }
+  class WorkgroupEditor implements Editor<Workgroup> {
+    SimpleEditor<String> label = SimpleEditor.of();
+    OptionalFieldEditor<List<Person>, ListEditor<Person, PersonEditor>> people = //
+    OptionalFieldEditor.of(ListEditor.<Person, PersonEditor> of( //
+    new EditorSource<PersonEditor>() {
+      @Override
+      public PersonEditor create(int index) {
+        return new PersonEditor();
+      }
+    }));
+  }
+
+  interface WorkgroupEditorDriver extends
+      SimpleBeanEditorDriver<Workgroup, WorkgroupEditor> {
+  }
+
+  /**
+   * CompositeEditors have an implementation complication due to the EditorChain
+   * needing to patch the composite editors into the hierarchy.
+   */
+  public void testDirtyOptionalList() {
+    WorkgroupEditorDriver driver = GWT.create(WorkgroupEditorDriver.class);
+    WorkgroupEditor editor = new WorkgroupEditor();
+    driver.initialize(editor);
+
+    Workgroup wg = new Workgroup();
+    driver.edit(wg);
+    assertFalse(driver.isDirty());
+
+    editor.people.setValue(Collections.singletonList(person));
+    assertTrue(driver.isDirty());
+  }
+
+  Person person;
+  Address personAddress;
+  Person manager;
+  long now;
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    personAddress = new Address();
+    personAddress.city = "City";
+    personAddress.street = "Street";
+
+    manager = new Person();
+    manager.name = "Bill";
+
+    person = new Person();
+    person.address = personAddress;
+    person.name = "Alice";
+    person.manager = manager;
+    person.localTime = now = System.currentTimeMillis();
+  }
+}
diff --git a/user/test/com/google/gwt/editor/client/EditorErrorTest.java b/user/test/com/google/gwt/editor/client/EditorErrorTest.java
index 136b75b..75f16b2 100644
--- a/user/test/com/google/gwt/editor/client/EditorErrorTest.java
+++ b/user/test/com/google/gwt/editor/client/EditorErrorTest.java
@@ -134,7 +134,7 @@
 
     List<EditorError> errors = editor.errors;
     assertNotNull(errors);
-    assertEquals(2, errors.size());
+    assertEquals("Wrong number of EditorErrors", 2, errors.size());
     assertEquals(Arrays.asList("people[0].address", "people[1].address"),
         Arrays.asList(errors.get(0).getPath(), errors.get(1).getPath()));
   }
diff --git a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
index b5f121f..2f5b990 100644
--- a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
+++ b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
@@ -118,7 +118,7 @@
       SimpleBeanEditorDriver<Person, PersonEditorWithCoAddressEditorView> {
   }
 
-  class PersonEditorWithDelegate extends PersonEditor implements
+  static class PersonEditorWithDelegate extends PersonEditor implements
       HasEditorDelegate<Person> {
     EditorDelegate<Person> delegate;
 
@@ -158,7 +158,7 @@
       SimpleBeanEditorDriver<Person, PersonEditorWithOptionalAddressEditor> {
   }
 
-  class PersonEditorWithOptionalAddressEditor implements Editor<Person> {
+  static class PersonEditorWithOptionalAddressEditor implements Editor<Person> {
     OptionalFieldEditor<Address, AddressEditor> address;
     SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED);
 
@@ -264,11 +264,10 @@
   Person person;
   Address personAddress;
   Person manager;
+  long now;
 
   static final String UNINITIALIZED = "uninitialized";
 
-  long now;
-
   @Override
   public String getModuleName() {
     return "com.google.gwt.editor.Editor";
@@ -322,78 +321,6 @@
     assertEquals("Should see this", person.getName());
   }
 
-  public void testDirty() {
-    PersonEditor editor = new PersonEditor();
-    PersonEditorDriver driver = GWT.create(PersonEditorDriver.class);
-    driver.initialize(editor);
-    driver.edit(person);
-
-    // Freshly-initialized should not be dirty
-    assertFalse(driver.isDirty());
-
-    // Changing the Person object should not affect the dirty status
-    person.setName("blah");
-    assertFalse(driver.isDirty());
-
-    editor.addressEditor.city.setValue("Foo");
-    assertTrue(driver.isDirty());
-
-    // Reset to original value
-    editor.addressEditor.city.setValue("City");
-    assertFalse(driver.isDirty());
-
-    // Try a null value
-    editor.managerName.setValue(null);
-    assertTrue(driver.isDirty());
-  }
-
-  public void testDirtyWithDelegate() {
-    PersonEditorWithDelegate editor = new PersonEditorWithDelegate();
-    PersonEditorWithDelegateDriver driver = GWT.create(PersonEditorWithDelegateDriver.class);
-    driver.initialize(editor);
-    driver.edit(person);
-
-    // Freshly-initialized should not be dirty
-    assertFalse(driver.isDirty());
-
-    // Use the delegate to toggle the state
-    editor.delegate.setDirty(true);
-    assertTrue(driver.isDirty());
-
-    // Use the delegate to clear the state
-    editor.delegate.setDirty(false);
-    assertFalse(driver.isDirty());
-
-    // Check that the delegate has no influence over values
-    editor.addressEditor.city.setValue("edited");
-    assertTrue(driver.isDirty());
-    editor.delegate.setDirty(false);
-    assertTrue(driver.isDirty());
-    editor.delegate.setDirty(true);
-    assertTrue(driver.isDirty());
-  }
-
-  public void testDirtyWithOptionalEditor() {
-    AddressEditor addressEditor = new AddressEditor();
-    PersonEditorWithOptionalAddressEditor editor = new PersonEditorWithOptionalAddressEditor(
-        addressEditor);
-    PersonEditorWithOptionalAddressDriver driver = GWT.create(PersonEditorWithOptionalAddressDriver.class);
-    driver.initialize(editor);
-    driver.edit(person);
-
-    // Freshly-initialized should not be dirty
-    assertFalse(driver.isDirty());
-
-    // Change the instance being edited
-    Address a = new Address();
-    editor.address.setValue(a);
-    assertTrue(driver.isDirty());
-
-    // Check restoration works
-    editor.address.setValue(personAddress);
-    assertFalse(driver.isDirty());
-  }
-
   /**
    * Test the use of the IsEditor interface that allows a view object to
    * encapsulate its Editor.
@@ -454,7 +381,7 @@
     // Runtime assignment of unexpected LeafValueEditor
     personEditor.addressEditor = addressEditor;
 
-    testLeafAddressEditor(driver, personEditor, addressEditor, false);
+    testLeafAddressEditor(driver, personEditor, addressEditor, true);
   }
 
   public void testLifecycle() {
@@ -666,7 +593,7 @@
     // Runtime assignment of unexpected LeafValueEditor
     personEditor.addressEditor = addressEditor;
 
-    testLeafAddressEditor(driver, personEditor, addressEditor, false);
+    testLeafAddressEditor(driver, personEditor, addressEditor, true);
     assertEquals(1, addressEditor.flushCalled);
     assertEquals(1, addressEditor.setDelegateCalled);
   }
@@ -699,20 +626,15 @@
     // Edit
     driver.edit(person);
     assertEquals(1, addressEditor.setValueCalled);
-    assertEquals(0, addressEditor.getValueCalled);
-    assertEquals(UNINITIALIZED, addressEditor.city.getValue());
+    // The DirtCollector will interrogate the leaf editors
+    assertEquals(1, addressEditor.getValueCalled);
 
     // Flush
     driver.flush();
     assertEquals(1, addressEditor.setValueCalled);
-    assertEquals(1, addressEditor.getValueCalled);
-    if (declaredAsLeaf) {
-      assertNotSame(oldAddress, person.address);
-      assertSame(person.address, addressEditor.value);
-    } else {
-      assertSame(oldAddress, person.address);
-      assertNotSame(person.address, addressEditor.value);
-    }
+    assertEquals(2, addressEditor.getValueCalled);
+    assertNotSame(oldAddress, person.address);
+    assertSame(person.address, addressEditor.value);
   }
 
   private <T extends Editor<Person>> void testValueAwareAddressEditor(
diff --git a/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java b/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java
index 3c4c704..7391e05 100644
--- a/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java
+++ b/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java
@@ -30,7 +30,7 @@
 import java.util.List;
 
 /**
- * 
+ * Tests for DelegateMap.
  */
 public class DelegateMapTest extends GWTTestCase {
   class AddressCoEditorView extends AddressEditor implements
@@ -82,27 +82,26 @@
     driver.initialize(editor);
     driver.edit(person);
 
-    map = driver.getDelegateMap();
+    map = DelegateMap.of(driver, DelegateMap.IDENTITY);
   }
 
   public void test() {
     // Test by-object
     assertEquals(Arrays.asList(editor), editors(map, person));
-    assertEquals(Arrays.asList(editor.addressEditor.addressEditor,
-        editor.addressEditor), editors(map, person.getAddress()));
+    assertEquals(
+        Arrays.asList(editor.addressEditor.addressEditor, editor.addressEditor),
+        editors(map, person.getAddress()));
 
     // Test by-path
     assertEquals(Arrays.asList(editor), editors(map, ""));
-    assertEquals(Arrays.asList(editor.addressEditor.addressEditor,
-        editor.addressEditor), editors(map, "address"));
+    assertEquals(
+        Arrays.asList(editor.addressEditor.addressEditor, editor.addressEditor),
+        editors(map, "address"));
   }
 
   public void testSimplePath() {
-    assertSame(editor.name, map.get(person).get(0).getSimpleEditor("name"));
-    assertSame(editor.managerName, map.get(person).get(0).getSimpleEditor(
-        "manager.name"));
-    // Only simple editors
-    assertNull(map.get(person).get(0).getSimpleEditor("address"));
+    assertSame(editor.name, map.getEditorByPath("name").get(0));
+    assertSame(editor.managerName, map.getEditorByPath("manager.name").get(0));
   }
 
   private List<Editor<?>> editors(DelegateMap map, Object o) {
@@ -115,7 +114,7 @@
 
   private List<Editor<?>> editors(DelegateMap map, String path) {
     List<Editor<?>> toReturn = new ArrayList<Editor<?>>();
-    for (AbstractEditorDelegate<?, ?> delegate : map.getPath(path)) {
+    for (AbstractEditorDelegate<?, ?> delegate : map.getDelegatesByPath(path)) {
       toReturn.add(delegate.getEditor());
     }
     return toReturn;
diff --git a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
index ad3afff..e390c3f 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -30,6 +30,7 @@
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.editor.client.CompositeEditor;
 import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDriver;
 import com.google.gwt.editor.client.EditorError;
 import com.google.gwt.editor.client.HasEditorDelegate;
 import com.google.gwt.editor.client.HasEditorErrors;
@@ -939,6 +940,7 @@
         new RealJavaResource(CompositeEditor.class),
         new EmptyMockJavaResource(ConstraintViolation.class),
         new RealJavaResource(Editor.class),
+        new EmptyMockJavaResource(EditorDriver.class),
         new RealJavaResource(EditorError.class),
         new EmptyMockJavaResource(EntityProxy.class),
         new EmptyMockJavaResource(EventBus.class),
diff --git a/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java b/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java
index e2b7ecd..7dc39f3 100644
--- a/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/ui/EditorTest.java
@@ -26,10 +26,12 @@
 import com.google.gwt.editor.client.adapters.EditorSource;
 import com.google.gwt.editor.client.adapters.ListEditor;
 import com.google.gwt.editor.client.adapters.SimpleEditor;
+import com.google.gwt.requestfactory.client.HasRequestContext;
 import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
 import com.google.gwt.requestfactory.client.RequestFactoryTestBase;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.SimpleBarProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooRequest;
@@ -49,8 +51,15 @@
    * DO NOT USE finishTest(). Instead, call finishTestAndReset();
    */
 
-  static class SimpleBarEditor implements Editor<SimpleBarProxy> {
+  static class SimpleBarEditor implements Editor<SimpleBarProxy>,
+      HasRequestContext<SimpleBarProxy> {
     protected final SimpleEditor<String> userName = SimpleEditor.of();
+    RequestContext ctx;
+
+    @Override
+    public void setRequestContext(RequestContext ctx) {
+      this.ctx = ctx;
+    }
   }
 
   static class SimpleFooBarOnlyEditor implements Editor<SimpleFooProxy> {
@@ -119,24 +128,28 @@
 
     final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
     driver.initialize(req, editor);
+    final String[] paths = driver.getPaths();
+    assertEquals(Arrays.asList("selfOneToManyField",
+        "selfOneToManyField.barField", "barField"), Arrays.asList(paths));
 
-    req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
+    req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
         new Receiver<SimpleFooProxy>() {
           @Override
           public void onSuccess(SimpleFooProxy response) {
 
             SimpleFooRequest context = req.simpleFooRequest();
             driver.edit(response, context);
-            context.persistAndReturnSelf().using(response).with(
-                driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
-              @Override
-              public void onSuccess(SimpleFooProxy response) {
-                assertEquals("EditorFooTest", response.getUserName());
-                assertEquals("EditorBarTest",
-                    response.getBarField().getUserName());
-                finishTestAndReset();
-              }
-            });
+            assertSame(context, editor.barEditor().ctx);
+            context.persistAndReturnSelf().using(response).with(paths).to(
+                new Receiver<SimpleFooProxy>() {
+                  @Override
+                  public void onSuccess(SimpleFooProxy response) {
+                    assertEquals("EditorFooTest", response.getUserName());
+                    assertEquals("EditorBarTest",
+                        response.getBarField().getUserName());
+                    finishTestAndReset();
+                  }
+                });
             assertEquals("GWT", editor.userName.getValue());
             assertEquals("FOO", editor.barEditor().userName.getValue());
             assertEquals("FOO", editor.barName.getValue());
@@ -210,7 +223,7 @@
     driver.initialize(req, editor);
 
     String[] paths = driver.getPaths();
-    assertEquals(Arrays.asList("barField.userName", "selfOneToManyField",
+    assertEquals(Arrays.asList("selfOneToManyField",
         "selfOneToManyField.barField", "barField"), Arrays.asList(paths));
 
     req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
@@ -235,11 +248,9 @@
             request.fire(new Receiver<SimpleFooProxy>() {
               @Override
               public void onSuccess(SimpleFooProxy response) {
-                System.out.println("B");
                 // EventBus notifications occur after the onSuccess()
                 Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
                   public boolean execute() {
-                    System.out.println("fo");
                     if ("updated".equals(editor.userName.getValue())) {
                       assertEquals("updated", editor.userName.getValue());
                       assertEquals("newBar",