blob: 911d743e80369ec4ffe907329e2f1b42c24e889f [file] [log] [blame]
/*
* Copyright 2010 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.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.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;
/**
* A base implementation of EditorDelegate for use by generated types.
*
* @param <T> the type of object being edited
* @param <E> the type of editor
*/
public abstract class AbstractEditorDelegate<T, E extends Editor<T>> implements
EditorDelegate<T> {
private 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>>();
public Chain(DelegateMap delegateMap) {
this.delegateMap = delegateMap;
}
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);
if (subDelegate == null) {
subDelegate = createComposedDelegate();
map.put(subEditor, subDelegate);
initializeSubDelegate(subDelegate, subPath, object, subEditor,
delegateMap);
} else {
subDelegate.path = subPath;
subDelegate.refresh(object);
}
}
public void detach(S subEditor) {
AbstractEditorDelegate<R, S> subDelegate = map.remove(subEditor);
if (subDelegate != null && subDelegate.shouldFlush()) {
subDelegate.flush(errors);
}
}
public R getValue(S subEditor) {
AbstractEditorDelegate<R, S> subDelegate = map.get(subEditor);
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();
}
}
}
protected static String appendPath(String prefix, String path) {
if ("".equals(prefix)) {
return path;
} else {
return prefix + "." + path;
}
}
protected CompositeEditor<T, Object, Editor<Object>> composedEditor;
protected Chain<Object, Editor<Object>> editorChain;
protected List<EditorError> errors;
protected HasEditorErrors<T> hasEditorErrors;
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;
public AbstractEditorDelegate() {
super();
}
/**
* 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 T getObject();
public String getPath() {
return path;
}
public void recordError(String message, Object value, Object userData) {
EditorError error = new SimpleError(this, message, value, userData);
errors.add(error);
}
public void recordError(String message, Object value, Object userData,
String extraPath) {
EditorError error = new SimpleError(this, message, value, userData,
extraPath);
errors.add(error);
}
public void refresh(T object) {
setObject(ensureMutable(object));
if (leafValueEditor != null) {
leafValueEditor.setValue(object);
} else if (valueAwareEditor != null) {
valueAwareEditor.setValue(object);
}
refreshEditors();
}
public abstract HandlerRegistration subscribe();
protected String appendPath(String path) {
return appendPath(this.path, path);
}
protected abstract void attachSubEditors(DelegateMap delegates);
/**
* Only implemented by delegates for a {@link CompositeEditor}.
*/
protected <C, D extends Editor<C>> AbstractEditorDelegate<C, D> createComposedDelegate() {
throw new IllegalStateException();
}
protected <Q> Q ensureMutable(Q object) {
return object;
}
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 void initialize(String pathSoFar, T object, E editor,
DelegateMap map) {
this.path = pathSoFar;
setEditor(editor);
setObject(ensureMutable(object));
errors = new ArrayList<EditorError>();
simpleEditors = new HashMap<String, Editor<?>>();
// 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) {
leafValueEditor.setValue(object);
return;
}
if (valueAwareEditor != null) {
valueAwareEditor.setValue(object);
}
if (object != null) {
attachSubEditors(map);
}
}
/**
* 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);
/**
* Refresh all of the sub-editors.
*/
protected abstract void refreshEditors();
protected abstract void setEditor(E editor);
protected abstract void setObject(T object);
/**
* Indicates whether or not calls to {@link #flush} are expected as part of
* normal operation.
*/
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();
}
}
}