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