/*
 * Copyright 2002-2017 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
 *
 *      https://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.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;

import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

Default implementation of the MessageCodesResolver interface.

Will create two message codes for an object error, in the following order (when using the prefixed formatter):

  • 1.: code + "." + object name
  • 2.: code

Will create four message codes for a field specification, in the following order:

  • 1.: code + "." + object name + "." + field
  • 2.: code + "." + field
  • 3.: code + "." + field type
  • 4.: code

For example, in case of code "typeMismatch", object name "user", field "age":

  • 1. try "typeMismatch.user.age"
  • 2. try "typeMismatch.age"
  • 3. try "typeMismatch.int"
  • 4. try "typeMismatch"

This resolution algorithm thus can be leveraged for example to show specific messages for binding errors like "required" and "typeMismatch":

  • at the object + field level ("age" field, but only on "user");
  • at the field level (all "age" fields, no matter which object name);
  • or at the general level (all fields, on any object).

In case of array, List or Map properties, both codes for specific elements and for the whole collection are generated. Assuming a field "name" of an array "groups" in object "user":

  • 1. try "typeMismatch.user.groups[0].name"
  • 2. try "typeMismatch.user.groups.name"
  • 3. try "typeMismatch.groups[0].name"
  • 4. try "typeMismatch.groups.name"
  • 5. try "typeMismatch.name"
  • 6. try "typeMismatch.java.lang.String"
  • 7. try "typeMismatch"

By default the errorCodes will be placed at the beginning of constructed message strings. The messageCodeFormatter property can be used to specify an alternative concatenation format.

In order to group all codes into a specific category within your resource bundles, e.g. "validation.typeMismatch.name" instead of the default "typeMismatch.name", consider specifying a prefix to be applied.

Author:Juergen Hoeller, Phillip Webb, Chris Beams
Since:1.0.1
/** * Default implementation of the {@link MessageCodesResolver} interface. * * <p>Will create two message codes for an object error, in the following order (when * using the {@link Format#PREFIX_ERROR_CODE prefixed} * {@link #setMessageCodeFormatter(MessageCodeFormatter) formatter}): * <ul> * <li>1.: code + "." + object name * <li>2.: code * </ul> * * <p>Will create four message codes for a field specification, in the following order: * <ul> * <li>1.: code + "." + object name + "." + field * <li>2.: code + "." + field * <li>3.: code + "." + field type * <li>4.: code * </ul> * * <p>For example, in case of code "typeMismatch", object name "user", field "age": * <ul> * <li>1. try "typeMismatch.user.age" * <li>2. try "typeMismatch.age" * <li>3. try "typeMismatch.int" * <li>4. try "typeMismatch" * </ul> * * <p>This resolution algorithm thus can be leveraged for example to show * specific messages for binding errors like "required" and "typeMismatch": * <ul> * <li>at the object + field level ("age" field, but only on "user"); * <li>at the field level (all "age" fields, no matter which object name); * <li>or at the general level (all fields, on any object). * </ul> * * <p>In case of array, {@link List} or {@link java.util.Map} properties, * both codes for specific elements and for the whole collection are * generated. Assuming a field "name" of an array "groups" in object "user": * <ul> * <li>1. try "typeMismatch.user.groups[0].name" * <li>2. try "typeMismatch.user.groups.name" * <li>3. try "typeMismatch.groups[0].name" * <li>4. try "typeMismatch.groups.name" * <li>5. try "typeMismatch.name" * <li>6. try "typeMismatch.java.lang.String" * <li>7. try "typeMismatch" * </ul> * * <p>By default the {@code errorCode}s will be placed at the beginning of constructed * message strings. The {@link #setMessageCodeFormatter(MessageCodeFormatter) * messageCodeFormatter} property can be used to specify an alternative concatenation * {@link MessageCodeFormatter format}. * * <p>In order to group all codes into a specific category within your resource bundles, * e.g. "validation.typeMismatch.name" instead of the default "typeMismatch.name", * consider specifying a {@link #setPrefix prefix} to be applied. * * @author Juergen Hoeller * @author Phillip Webb * @author Chris Beams * @since 1.0.1 */
@SuppressWarnings("serial") public class DefaultMessageCodesResolver implements MessageCodesResolver, Serializable {
The separator that this implementation uses when resolving message codes.
/** * The separator that this implementation uses when resolving message codes. */
public static final String CODE_SEPARATOR = "."; private static final MessageCodeFormatter DEFAULT_FORMATTER = Format.PREFIX_ERROR_CODE; private String prefix = ""; private MessageCodeFormatter formatter = DEFAULT_FORMATTER;
Specify a prefix to be applied to any code built by this resolver.

Default is none. Specify, for example, "validation." to get error codes like "validation.typeMismatch.name".

/** * Specify a prefix to be applied to any code built by this resolver. * <p>Default is none. Specify, for example, "validation." to get * error codes like "validation.typeMismatch.name". */
public void setPrefix(@Nullable String prefix) { this.prefix = (prefix != null ? prefix : ""); }
Return the prefix to be applied to any code built by this resolver.

Returns an empty String in case of no prefix.

/** * Return the prefix to be applied to any code built by this resolver. * <p>Returns an empty String in case of no prefix. */
protected String getPrefix() { return this.prefix; }
Specify the format for message codes built by this resolver.

The default is Format.PREFIX_ERROR_CODE.

See Also:
Since:3.2
/** * Specify the format for message codes built by this resolver. * <p>The default is {@link Format#PREFIX_ERROR_CODE}. * @since 3.2 * @see Format */
public void setMessageCodeFormatter(@Nullable MessageCodeFormatter formatter) { this.formatter = (formatter != null ? formatter : DEFAULT_FORMATTER); } @Override public String[] resolveMessageCodes(String errorCode, String objectName) { return resolveMessageCodes(errorCode, objectName, "", null); }
Build the code list for the given code and field: an object/field-specific code, a field-specific code, a plain error code.

Arrays, Lists and Maps are resolved both for specific elements and the whole collection.

See the class level javadoc for details on the generated codes.

Returns:the list of codes
/** * Build the code list for the given code and field: an * object/field-specific code, a field-specific code, a plain error code. * <p>Arrays, Lists and Maps are resolved both for specific elements and * the whole collection. * <p>See the {@link DefaultMessageCodesResolver class level javadoc} for * details on the generated codes. * @return the list of codes */
@Override public String[] resolveMessageCodes(String errorCode, String objectName, String field, @Nullable Class<?> fieldType) { Set<String> codeList = new LinkedHashSet<>(); List<String> fieldList = new ArrayList<>(); buildFieldList(field, fieldList); addCodes(codeList, errorCode, objectName, fieldList); int dotIndex = field.lastIndexOf('.'); if (dotIndex != -1) { buildFieldList(field.substring(dotIndex + 1), fieldList); } addCodes(codeList, errorCode, null, fieldList); if (fieldType != null) { addCode(codeList, errorCode, null, fieldType.getName()); } addCode(codeList, errorCode, null, null); return StringUtils.toStringArray(codeList); } private void addCodes(Collection<String> codeList, String errorCode, @Nullable String objectName, Iterable<String> fields) { for (String field : fields) { addCode(codeList, errorCode, objectName, field); } } private void addCode(Collection<String> codeList, String errorCode, @Nullable String objectName, @Nullable String field) { codeList.add(postProcessMessageCode(this.formatter.format(errorCode, objectName, field))); }
Add both keyed and non-keyed entries for the supplied field to the supplied field list.
/** * Add both keyed and non-keyed entries for the supplied {@code field} * to the supplied field list. */
protected void buildFieldList(String field, List<String> fieldList) { fieldList.add(field); String plainField = field; int keyIndex = plainField.lastIndexOf('['); while (keyIndex != -1) { int endKeyIndex = plainField.indexOf(']', keyIndex); if (endKeyIndex != -1) { plainField = plainField.substring(0, keyIndex) + plainField.substring(endKeyIndex + 1); fieldList.add(plainField); keyIndex = plainField.lastIndexOf('['); } else { keyIndex = -1; } } }
Post-process the given message code, built by this resolver.

The default implementation applies the specified prefix, if any.

Params:
  • code – the message code as built by this resolver
See Also:
Returns:the final message code to be returned
/** * Post-process the given message code, built by this resolver. * <p>The default implementation applies the specified prefix, if any. * @param code the message code as built by this resolver * @return the final message code to be returned * @see #setPrefix */
protected String postProcessMessageCode(String code) { return getPrefix() + code; }
Common message code formats.
See Also:
/** * Common message code formats. * @see MessageCodeFormatter * @see DefaultMessageCodesResolver#setMessageCodeFormatter(MessageCodeFormatter) */
public enum Format implements MessageCodeFormatter {
Prefix the error code at the beginning of the generated message code. e.g.: errorCode + "." + object name + "." + field
/** * Prefix the error code at the beginning of the generated message code. e.g.: * {@code errorCode + "." + object name + "." + field} */
PREFIX_ERROR_CODE { @Override public String format(String errorCode, @Nullable String objectName, @Nullable String field) { return toDelimitedString(errorCode, objectName, field); } },
Postfix the error code at the end of the generated message code. e.g.: object name + "." + field + "." + errorCode
/** * Postfix the error code at the end of the generated message code. e.g.: * {@code object name + "." + field + "." + errorCode} */
POSTFIX_ERROR_CODE { @Override public String format(String errorCode, @Nullable String objectName, @Nullable String field) { return toDelimitedString(objectName, field, errorCode); } };
Concatenate the given elements, delimiting each with DefaultMessageCodesResolver.CODE_SEPARATOR, skipping zero-length or null elements altogether.
/** * Concatenate the given elements, delimiting each with * {@link DefaultMessageCodesResolver#CODE_SEPARATOR}, skipping zero-length or * null elements altogether. */
public static String toDelimitedString(String... elements) { StringJoiner rtn = new StringJoiner(CODE_SEPARATOR); for (String element : elements) { if (StringUtils.hasLength(element)) { rtn.add(element); } } return rtn.toString(); } } }