001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.validation;
018:
019: import java.io.Serializable;
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023:
024: import org.springframework.util.StringUtils;
025:
026: /**
027: * Default implementation of the {@link MessageCodesResolver} interface.
028: *
029: * <p>Will create two message codes for an object error, in the following order:
030: * <ul>
031: * <li>1.: code + "." + object name
032: * <li>2.: code
033: * </ul>
034: *
035: * <p>Will create four message codes for a field specification, in the following order:
036: * <ul>
037: * <li>1.: code + "." + object name + "." + field
038: * <li>2.: code + "." + field
039: * <li>3.: code + "." + field type
040: * <li>4.: code
041: * </ul>
042: *
043: * <p>For example, in case of code "typeMismatch", object name "user", field "age":
044: * <ul>
045: * <li>1. try "typeMismatch.user.age"
046: * <li>2. try "typeMismatch.age"
047: * <li>3. try "typeMismatch.int"
048: * <li>4. try "typeMismatch"
049: * </ul>
050: *
051: * <p>This resolution algorithm thus can be leveraged for example to show
052: * specific messages for binding errors like "required" and "typeMismatch":
053: * <ul>
054: * <li>at the object + field level ("age" field, but only on "user");
055: * <li>at the field level (all "age" fields, no matter which object name);
056: * <li>or at the general level (all fields, on any object).
057: * </ul>
058: *
059: * <p>In case of array, {@link List} or {@link java.util.Map} properties,
060: * both codes for specific elements and for the whole collection are
061: * generated. Assuming a field "name" of an array "groups" in object "user":
062: * <ul>
063: * <li>1. try "typeMismatch.user.groups[0].name"
064: * <li>2. try "typeMismatch.user.groups.name"
065: * <li>3. try "typeMismatch.groups[0].name"
066: * <li>4. try "typeMismatch.groups.name"
067: * <li>5. try "typeMismatch.name"
068: * <li>6. try "typeMismatch.java.lang.String"
069: * <li>7. try "typeMismatch"
070: * </ul>
071: *
072: * <p>In order to group all codes into a specific category within your resource bundles,
073: * e.g. "validation.typeMismatch.name" instead of the default "typeMismatch.name",
074: * consider specifying a {@link #setPrefix prefix} to be applied.
075: *
076: * @author Juergen Hoeller
077: * @since 1.0.1
078: */
079: public class DefaultMessageCodesResolver implements
080: MessageCodesResolver, Serializable {
081:
082: /**
083: * The separator that this implementation uses when resolving message codes.
084: */
085: public static final String CODE_SEPARATOR = ".";
086:
087: private String prefix = "";
088:
089: /**
090: * Specify a prefix to be applied to any code built by this resolver.
091: * <p>Default is none. Specify, for example, "validation." to get
092: * error codes like "validation.typeMismatch.name".
093: */
094: public void setPrefix(String prefix) {
095: this .prefix = (prefix != null ? prefix : "");
096: }
097:
098: /**
099: * Return the prefix to be applied to any code built by this resolver.
100: * <p>Returns an empty String in case of no prefix.
101: */
102: protected String getPrefix() {
103: return this .prefix;
104: }
105:
106: public String[] resolveMessageCodes(String errorCode,
107: String objectName) {
108: return new String[] {
109: postProcessMessageCode(errorCode + CODE_SEPARATOR
110: + objectName),
111: postProcessMessageCode(errorCode) };
112: }
113:
114: /**
115: * Build the code list for the given code and field: an
116: * object/field-specific code, a field-specific code, a plain error code.
117: * <p>Arrays, Lists and Maps are resolved both for specific elements and
118: * the whole collection.
119: * <p>See the {@link DefaultMessageCodesResolver class level Javadoc} for
120: * details on the generated codes.
121: * @return the list of codes
122: */
123: public String[] resolveMessageCodes(String errorCode,
124: String objectName, String field, Class fieldType) {
125: List codeList = new ArrayList();
126: List fieldList = new ArrayList();
127: buildFieldList(field, fieldList);
128: for (Iterator it = fieldList.iterator(); it.hasNext();) {
129: String fieldInList = (String) it.next();
130: codeList.add(postProcessMessageCode(errorCode
131: + CODE_SEPARATOR + objectName + CODE_SEPARATOR
132: + fieldInList));
133: }
134: int dotIndex = field.lastIndexOf('.');
135: if (dotIndex != -1) {
136: buildFieldList(field.substring(dotIndex + 1), fieldList);
137: }
138: for (Iterator it = fieldList.iterator(); it.hasNext();) {
139: String fieldInList = (String) it.next();
140: codeList.add(postProcessMessageCode(errorCode
141: + CODE_SEPARATOR + fieldInList));
142: }
143: if (fieldType != null) {
144: codeList.add(postProcessMessageCode(errorCode
145: + CODE_SEPARATOR + fieldType.getName()));
146: }
147: codeList.add(postProcessMessageCode(errorCode));
148: return StringUtils.toStringArray(codeList);
149: }
150:
151: /**
152: * Add both keyed and non-keyed entries for the supplied <code>field</code>
153: * to the supplied field list.
154: */
155: protected void buildFieldList(String field, List fieldList) {
156: fieldList.add(field);
157: String plainField = field;
158: int keyIndex = plainField.lastIndexOf('[');
159: while (keyIndex != -1) {
160: int endKeyIndex = plainField.indexOf(']', keyIndex);
161: if (endKeyIndex != -1) {
162: plainField = plainField.substring(0, keyIndex)
163: + plainField.substring(endKeyIndex + 1);
164: fieldList.add(plainField);
165: keyIndex = plainField.lastIndexOf('[');
166: } else {
167: keyIndex = -1;
168: }
169: }
170: }
171:
172: /**
173: * Post-process the given message code, built by this resolver.
174: * <p>The default implementation applies the specified prefix, if any.
175: * @param code the message code as built by this resolver
176: * @return the final message code to be returned
177: * @see #setPrefix
178: */
179: protected String postProcessMessageCode(String code) {
180: return getPrefix() + code;
181: }
182:
183: }
|