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<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",