| /* |
| * 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(); |
| } |