Open Source Repository

Home /spring/spring-context-3.0.5 | Repository Home



org/springframework/validation/AbstractBindingResult.java
/*
 * Copyright 2002-2010 the original author or authors.
 *
 * 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 org.springframework.validation;

import java.beans.PropertyEditor;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Abstract implementation of the {@link BindingResult} interface and
 * its super-interface {@link Errors}. Encapsulates common management of
 {@link ObjectError ObjectErrors} and {@link FieldError FieldErrors}.
 *
 @author Juergen Hoeller
 @author Rob Harrop
 @since 2.0
 @see Errors
 */
public abstract class AbstractBindingResult extends AbstractErrors implements BindingResult, Serializable {

  private final String objectName;

  private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();

  private final List<ObjectError> errors = new LinkedList<ObjectError>();

  private final Set<String> suppressedFields = new HashSet<String>();


  /**
   * Create a new AbstractBindingResult instance.
   @param objectName the name of the target object
   @see DefaultMessageCodesResolver
   */
  protected AbstractBindingResult(String objectName) {
    this.objectName = objectName;
  }

  /**
   * Set the strategy to use for resolving errors into message codes.
   * Default is DefaultMessageCodesResolver.
   @see DefaultMessageCodesResolver
   */
  public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
    Assert.notNull(messageCodesResolver, "MessageCodesResolver must not be null");
    this.messageCodesResolver = messageCodesResolver;
  }

  /**
   * Return the strategy to use for resolving errors into message codes.
   */
  public MessageCodesResolver getMessageCodesResolver() {
    return this.messageCodesResolver;
  }


  //---------------------------------------------------------------------
  // Implementation of the Errors interface
  //---------------------------------------------------------------------

  public String getObjectName() {
    return this.objectName;
  }


  public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
    addError(new ObjectError(getObjectName(), resolveMessageCodes(errorCode), errorArgs, defaultMessage));
  }

  public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
    if ("".equals(getNestedPath()) && !StringUtils.hasLength(field)) {
      // We're at the top of the nested object hierarchy,
      // so the present level is not a field but rather the top object.
      // The best we can do is register a global error here...
      reject(errorCode, errorArgs, defaultMessage);
      return;
    }
    String fixedField = fixedField(field);
    Object newVal = getActualFieldValue(fixedField);
    FieldError fe = new FieldError(
        getObjectName(), fixedField, newVal, false,
        resolveMessageCodes(errorCode, field), errorArgs, defaultMessage);
    addError(fe);
  }

  public void addError(ObjectError error) {
    this.errors.add(error);
  }

  public void addAllErrors(Errors errors) {
    if (!errors.getObjectName().equals(getObjectName())) {
      throw new IllegalArgumentException("Errors object needs to have same object name");
    }
    this.errors.addAll(errors.getAllErrors());
  }

  /**
   * Resolve the given error code into message codes.
   * Calls the MessageCodesResolver with appropriate parameters.
   @param errorCode the error code to resolve into message codes
   @return the resolved message codes
   @see #setMessageCodesResolver
   */
  public String[] resolveMessageCodes(String errorCode) {
    return getMessageCodesResolver().resolveMessageCodes(errorCode, getObjectName());
  }

  public String[] resolveMessageCodes(String errorCode, String field) {
    Class fieldType = getFieldType(field);
    return getMessageCodesResolver().resolveMessageCodes(
        errorCode, getObjectName(), fixedField(field), fieldType);
  }


  @Override
  public boolean hasErrors() {
    return !this.errors.isEmpty();
  }

  @Override
  public int getErrorCount() {
    return this.errors.size();
  }

  @Override
  public List<ObjectError> getAllErrors() {
    return Collections.unmodifiableList(this.errors);
  }

  public List<ObjectError> getGlobalErrors() {
    List<ObjectError> result = new LinkedList<ObjectError>();
    for (ObjectError objectError : this.errors) {
      if (!(objectError instanceof FieldError)) {
        result.add(objectError);
      }
    }
    return Collections.unmodifiableList(result);
  }

  @Override
  public ObjectError getGlobalError() {
    for (ObjectError objectError : this.errors) {
      if (!(objectError instanceof FieldError)) {
        return objectError;
      }
    }
    return null;
  }

  public List<FieldError> getFieldErrors() {
    List<FieldError> result = new LinkedList<FieldError>();
    for (ObjectError objectError : this.errors) {
      if (objectError instanceof FieldError) {
        result.add((FieldErrorobjectError);
      }
    }
    return Collections.unmodifiableList(result);
  }

  @Override
  public FieldError getFieldError() {
    for (ObjectError objectError : this.errors) {
      if (objectError instanceof FieldError) {
        return (FieldErrorobjectError;
      }
    }
    return null;
  }

  @Override
  public List<FieldError> getFieldErrors(String field) {
    List<FieldError> result = new LinkedList<FieldError>();
    String fixedField = fixedField(field);
    for (ObjectError objectError : this.errors) {
      if (objectError instanceof FieldError && isMatchingFieldError(fixedField, (FieldErrorobjectError)) {
        result.add((FieldErrorobjectError);
      }
    }
    return Collections.unmodifiableList(result);
  }

  @Override
  public FieldError getFieldError(String field) {
    String fixedField = fixedField(field);
    for (ObjectError objectError : this.errors) {
      if (objectError instanceof FieldError) {
        FieldError fieldError = (FieldErrorobjectError;
        if (isMatchingFieldError(fixedField, fieldError)) {
          return fieldError;
        }
      }
    }
    return null;
  }

  public Object getFieldValue(String field) {
    FieldError fieldError = getFieldError(field);
    // Use rejected value in case of error, current bean property value else.
    Object value = (fieldError != null ? fieldError.getRejectedValue() :
        getActualFieldValue(fixedField(field)));
    // Apply formatting, but not on binding failures like type mismatches.
    if (fieldError == null || !fieldError.isBindingFailure()) {
      value = formatFieldValue(field, value);
    }
    return value;
  }

  /**
   * This default implementation determines the type based on the actual
   * field value, if any. Subclasses should override this to determine
   * the type from a descriptor, even for <code>null</code> values.
   @see #getActualFieldValue
   */
  @Override
  public Class getFieldType(String field) {
    Object value = getActualFieldValue(fixedField(field));
    if (value != null) {
      return value.getClass();
    }
    return null;
  }


  //---------------------------------------------------------------------
  // Implementation of BindingResult interface
  //---------------------------------------------------------------------

  /**
   * Return a model Map for the obtained state, exposing an Errors
   * instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName'
   * and the object itself.
   <p>Note that the Map is constructed every time you're calling this method.
   * Adding things to the map and then re-calling this method will not work.
   <p>The attributes in the model Map returned by this method are usually
   * included in the ModelAndView for a form view that uses Spring's bind tag,
   * which needs access to the Errors instance. Spring's SimpleFormController
   * will do this for you when rendering its form or success view. When
   * building the ModelAndView yourself, you need to include the attributes
   * from the model Map returned by this method yourself.
   @see #getObjectName
   @see #MODEL_KEY_PREFIX
   @see org.springframework.web.servlet.ModelAndView
   @see org.springframework.web.servlet.tags.BindTag
   @see org.springframework.web.servlet.mvc.SimpleFormController
   */
  public Map<String, Object> getModel() {
    Map<String, Object> model = new HashMap<String, Object>(2);
    // Errors instance, even if no errors.
    model.put(MODEL_KEY_PREFIX + getObjectName()this);
    // Mapping from name to target object.
    model.put(getObjectName(), getTarget());
    return model;
  }

  public Object getRawFieldValue(String field) {
    return getActualFieldValue(fixedField(field));
  }

  /**
   * This implementation delegates to the
   {@link #getPropertyEditorRegistry() PropertyEditorRegistry}'s
   * editor lookup facility, if available.
   */
  public PropertyEditor findEditor(String field, Class valueType) {
    PropertyEditorRegistry editorRegistry = getPropertyEditorRegistry();
    if (editorRegistry != null) {
      Class valueTypeToUse = valueType;
      if (valueTypeToUse == null) {
        valueTypeToUse = getFieldType(field);
      }
      return editorRegistry.findCustomEditor(valueTypeToUse, fixedField(field));
    }
    else {
      return null;
    }
  }

  /**
   * This implementation returns <code>null</code>.
   */
  public PropertyEditorRegistry getPropertyEditorRegistry() {
    return null;
  }

  /**
   * Mark the specified disallowed field as suppressed.
   <p>The data binder invokes this for each field value that was
   * detected to target a disallowed field.
   @see DataBinder#setAllowedFields
   */
  public void recordSuppressedField(String field) {
    this.suppressedFields.add(field);
  }

  /**
   * Return the list of fields that were suppressed during the bind process.
   <p>Can be used to determine whether any field values were targetting
   * disallowed fields.
   @see DataBinder#setAllowedFields
   */
  public String[] getSuppressedFields() {
    return StringUtils.toStringArray(this.suppressedFields);
  }


  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (!(other instanceof BindingResult)) {
      return false;
    }
    BindingResult otherResult = (BindingResultother;
    return (getObjectName().equals(otherResult.getObjectName()) &&
        getTarget().equals(otherResult.getTarget()) &&
        getAllErrors().equals(otherResult.getAllErrors()));
  }

  @Override
  public int hashCode() {
    return getObjectName().hashCode() 29 + getTarget().hashCode();
  }


  //---------------------------------------------------------------------
  // Template methods to be implemented/overridden by subclasses
  //---------------------------------------------------------------------

  /**
   * Return the wrapped target object.
   */
  public abstract Object getTarget();

  /**
   * Extract the actual field value for the given field.
   @param field the field to check
   @return the current value of the field
   */
  protected abstract Object getActualFieldValue(String field);

  /**
   * Format the given value for the specified field.
   <p>The default implementation simply returns the field value as-is.
   @param field the field to check
   @param value the value of the field (either a rejected value
   * other than from a binding error, or an actual field value)
   @return the formatted value
   */
  protected Object formatFieldValue(String field, Object value) {
    return value;
  }

}