blob: 803980fb7c7337c93b1a6937d9dfa41a1221087b [file] [log] [blame]
/*
* 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.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;
import javax.validation.ConstraintViolation;
/**
* Abstraction of a ConstraintViolation or a RequestFactory Violation object.
* Also contains a factory method to create SimpleViolation instances from
* {@link ConstraintViolation} objects.
*/
public abstract class SimpleViolation {
/**
* Provides a source of SimpleViolation objects based on ConstraintViolations.
* This is re-used by the RequestFactoryEditorDriver implementation, which
* does not share a type hierarchy with the SimpleBeanEditorDriver.
*/
static class ConstraintViolationIterable implements Iterable<SimpleViolation> {
private final Iterable<ConstraintViolation<?>> violations;
public ConstraintViolationIterable(
Iterable<ConstraintViolation<?>> violations) {
this.violations = violations;
}
public Iterator<SimpleViolation> iterator() {
// Use a fresh source iterator each time
final Iterator<ConstraintViolation<?>> source = violations.iterator();
return new Iterator<SimpleViolation>() {
public boolean hasNext() {
return source.hasNext();
}
public SimpleViolation next() {
return new SimpleViolationAdapter(source.next());
}
public void remove() {
source.remove();
}
};
}
}
/**
* Adapts the ConstraintViolation interface to the SimpleViolation interface.
*/
static class SimpleViolationAdapter extends SimpleViolation {
private final ConstraintViolation<?> v;
public SimpleViolationAdapter(ConstraintViolation<?> v) {
this.v = v;
}
@Override
public Object getKey() {
return v.getRootBean();
}
@Override
public String getMessage() {
return v.getMessage();
}
@Override
public String getPath() {
/*
* TODO(bobv,nchalko): Determine the correct way to extract this
* information from the ConstraintViolation.
*/
return v.getPropertyPath().toString();
}
@Override
public Object getUserDataObject() {
return v;
}
}
public static Iterable<SimpleViolation> iterableFromConstrantViolations(
Iterable<ConstraintViolation<?>> violations) {
return new ConstraintViolationIterable(violations);
}
/**
* Maps an abstract representation of a violation into the appropriate
* EditorDelegate.
*/
public static void pushViolations(Iterable<SimpleViolation> violations,
EditorDriver<?> driver, KeyMethod keyMethod) {
if (violations == null) {
return;
}
DelegateMap delegateMap = DelegateMap.of(driver, keyMethod);
// For each violation
for (SimpleViolation error : violations) {
Object key = error.getKey();
List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.get(key);
if (delegateList != null) {
// For each delegate editing some record...
for (AbstractEditorDelegate<?, ?> baseDelegate : delegateList) {
// compute its base path in the hierarchy...
String basePath = baseDelegate.getPath();
// and the absolute path of the leaf editor receiving the error.
String absolutePath = (basePath.length() > 0 ? basePath + "." : "")
+ error.getPath();
final String originalAbsolutePath = absolutePath;
while (true) {
if (processLeafDelegates(
delegateMap, originalAbsolutePath, absolutePath, error)) {
break;
} else if (processEditors(
delegateMap, baseDelegate, absolutePath, error)) {
break;
} else {
// This is guaranteed to never happen because we should always
// process a delegate/editor if the absolutePath is empty.
// Still, we have the check here to prevent an infinite
// loop if something goes wrong.
if (absolutePath.isEmpty()) {
throw new IllegalStateException(
"No editor: " + originalAbsolutePath);
}
absolutePath = getParentPath(absolutePath);
}
}
}
}
}
}
/**
* Returns the path with everything after the last '.' stripped off,
* or "" if there was no '.' in the path.
*/
private static String getParentPath(String absolutePath) {
// Traverse upwards in the path to the parents in order
// to report the error to the nearest valid parent.
int dotIdx = absolutePath.lastIndexOf('.');
if (dotIdx > 0) {
return absolutePath.substring(0, dotIdx);
}
return "";
}
/**
* Records an error in any editors that match the path, returning true
* if any editors matched.
*/
private static boolean processEditors(DelegateMap delegateMap,
AbstractEditorDelegate<?, ?> baseDelegate,
String absolutePath,
SimpleViolation error) {
List<Editor<?>> editors = delegateMap.getEditorByPath(absolutePath);
if (editors == null) {
return false;
}
// No EditorDelegate to attach it to, so record on the baseDelegate
// with the appropriate editor & path.
for (Editor<?> editor : editors) {
baseDelegate.recordError(error.getMessage(), null,
error.getUserDataObject(), error.getPath(), editor);
}
return true;
}
/**
* Records an error in any delegates that match the {@code absolutePath},
* returning true if any matched. ({@code originalAbsolutePath}
* is not used for finding delegates, but is instead used to determine
* how to record the error.)
*/
private static boolean processLeafDelegates(DelegateMap delegateMap,
String originalAbsolutePath,
String absolutePath,
SimpleViolation error) {
// Find the leaf editor's delegate.
List<AbstractEditorDelegate<?, ?>> leafDelegates =
delegateMap.getDelegatesByPath(absolutePath);
if (leafDelegates == null) {
return false;
}
String addlPath = originalAbsolutePath.substring(absolutePath.length());
for (AbstractEditorDelegate<?, ?> delegate : leafDelegates) {
// If this is the original path value, don't record the additional path.
if (addlPath.isEmpty()) {
delegate.recordError(error.getMessage(), null,
error.getUserDataObject());
} else {
// Otherwise, include the additional path.
delegate.recordError(error.getMessage(), null,
error.getUserDataObject(), addlPath,
delegate.getEditor());
}
}
return true;
}
/**
* Typically constructed via factory methods.
*/
protected SimpleViolation() {
}
/**
* Return the object that the violation is about.
*/
public abstract Object getKey();
/**
* Return a user-facing message describing the violation.
*/
public abstract String getMessage();
/**
* Return a dotted path describing the property.
*/
public abstract String getPath();
/**
* An object that should be available from
* {@link com.google.gwt.editor.client.EditorError#getUserData()}.
*/
public abstract Object getUserDataObject();
}