From 21760a8b6b030233d4a82d8026bc9910e0a93ea5 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 15 Oct 2012 11:29:12 -0400 Subject: [PATCH] Provide alternative message code resolver styles Introduce new 'style' property to DefaultMessageCodesResolver allowing for alternative message styles. Current styles are PREFIX_ERROR_CODE and POSTFIX_ERROR_CODE. The default style retains existing behavior. Issue: SPR-9707 --- .../DefaultMessageCodesResolver.java | 94 ++++++++++++++++--- .../DefaultMessageCodesResolverTests.java | 24 ++++- 2 files changed, 103 insertions(+), 15 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/DefaultMessageCodesResolver.java b/spring-context/src/main/java/org/springframework/validation/DefaultMessageCodesResolver.java index ad282eb1d1..44d8b65c72 100644 --- a/spring-context/src/main/java/org/springframework/validation/DefaultMessageCodesResolver.java +++ b/spring-context/src/main/java/org/springframework/validation/DefaultMessageCodesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2012 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. @@ -18,14 +18,18 @@ 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 org.springframework.util.StringUtils; /** * Default implementation of the {@link MessageCodesResolver} interface. * - *

Will create two message codes for an object error, in the following order: + *

Will create two message codes for an object error, in the following order (when + * using the {@link Style#PREFIX_ERROR_CODE prefixed} {@link #setStyle(Style) style}): *

* + *

By default the {@code errorCode}s will be placed at the beginning of constructed + * message strings. The {@link #setStyle(Style) style} property can be used to specify + * alternative {@link Style styles} of concatination. + * *

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 * @since 1.0.1 */ @SuppressWarnings("serial") @@ -83,9 +92,13 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial */ public static final String CODE_SEPARATOR = "."; + private static final Style DEFAULT_STYLE = Style.PREFIX_ERROR_CODE; + private String prefix = ""; + private Style style = DEFAULT_STYLE; + /** * Specify a prefix to be applied to any code built by this resolver. @@ -96,6 +109,14 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial this.prefix = (prefix != null ? prefix : ""); } + /** + * Specify the style of message code that will be built by this resolver. + *

Default is {@link Style#PREFIX_ERROR_CODE}. + */ + public void setStyle(Style style) { + this.style = (style == null ? DEFAULT_STYLE : style); + } + /** * Return the prefix to be applied to any code built by this resolver. *

Returns an empty String in case of no prefix. @@ -106,9 +127,7 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial public String[] resolveMessageCodes(String errorCode, String objectName) { - return new String[] { - postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName), - postProcessMessageCode(errorCode)}; + return resolveMessageCodes(errorCode, objectName, "", null); } /** @@ -121,26 +140,54 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial * @return the list of codes */ public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) { - List codeList = new ArrayList(); + Set codeList = new LinkedHashSet(); List fieldList = new ArrayList(); buildFieldList(field, fieldList); - for (String fieldInList : fieldList) { - codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + fieldInList)); - } + addCodes(codeList, errorCode, objectName, fieldList); int dotIndex = field.lastIndexOf('.'); if (dotIndex != -1) { buildFieldList(field.substring(dotIndex + 1), fieldList); } - for (String fieldInList : fieldList) { - codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldInList)); - } + addCodes(codeList, errorCode, null, fieldList); if (fieldType != null) { - codeList.add(postProcessMessageCode(errorCode + CODE_SEPARATOR + fieldType.getName())); + addCode(codeList, errorCode, null, fieldType.getName()); } - codeList.add(postProcessMessageCode(errorCode)); + addCode(codeList, errorCode, null, null); return StringUtils.toStringArray(codeList); } + private void addCodes(Collection codeList, String errorCode, String objectName, Iterable fields) { + for (String field : fields) { + addCode(codeList, errorCode, objectName, field); + } + } + + private void addCode(Collection codeList, String errorCode, String objectName, String field) { + String code = getCode(errorCode, objectName, field); + codeList.add(postProcessMessageCode(code)); + } + + private String getCode(String errorCode, String objectName, String field) { + switch (this.style) { + case PREFIX_ERROR_CODE: + return toDelimitedString(errorCode, objectName, field); + case POSTFIX_ERROR_CODE: + return toDelimitedString(objectName, field, errorCode); + } + throw new IllegalStateException("Unknown style " + this.style); + } + + private String toDelimitedString(String... elements) { + StringBuilder rtn = new StringBuilder(); + for (String element : elements) { + if(StringUtils.hasLength(element)) { + rtn.append(rtn.length() == 0 ? "" : CODE_SEPARATOR); + rtn.append(element); + } + } + return rtn.toString(); + } + /** * Add both keyed and non-keyed entries for the supplied field * to the supplied field list. @@ -173,4 +220,23 @@ public class DefaultMessageCodesResolver implements MessageCodesResolver, Serial return getPrefix() + code; } + + /** + * The various styles that can be used to construct message codes. + */ + public static enum Style { + + /** + * Prefix the error code at the beginning of the generated message code. eg: + * {@code errorCode + "." + object name + "." + field} + */ + PREFIX_ERROR_CODE, + + /** + * Postfix the error code at the end of the generated message code. eg: + * {@code object name + "." + field + "." + errorCode} + */ + POSTFIX_ERROR_CODE + } + } diff --git a/spring-context/src/test/java/org/springframework/validation/DefaultMessageCodesResolverTests.java b/spring-context/src/test/java/org/springframework/validation/DefaultMessageCodesResolverTests.java index 33083409ae..18bb109b5a 100644 --- a/spring-context/src/test/java/org/springframework/validation/DefaultMessageCodesResolverTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DefaultMessageCodesResolverTests.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertThat; import org.junit.Test; import org.springframework.beans.TestBean; +import org.springframework.validation.DefaultMessageCodesResolver.Style; /** * Tests for {@link DefaultMessageCodesResolver}. @@ -119,5 +120,26 @@ public class DefaultMessageCodesResolverTests { "errorCode.objectName.field", "errorCode.field", "errorCode" }))); - } + } + + @Test + public void shouldSupportPostfixStyle() throws Exception { + resolver.setStyle(Style.POSTFIX_ERROR_CODE); + String[] codes = resolver.resolveMessageCodes("errorCode", "objectName"); + assertThat(codes, is(equalTo(new String[] { + "objectName.errorCode", + "errorCode" }))); + } + + @Test + public void shouldSupportFieldPostfixStyle() throws Exception { + resolver.setStyle(Style.POSTFIX_ERROR_CODE); + String[] codes = resolver.resolveMessageCodes("errorCode", "objectName", "field", + TestBean.class); + assertThat(codes, is(equalTo(new String[] { + "objectName.field.errorCode", + "field.errorCode", + "org.springframework.beans.TestBean.errorCode", + "errorCode" }))); + } }