001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.validation;
018
019import java.io.Serializable;
020import java.util.Collections;
021import java.util.EmptyStackException;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Stack;
025
026import org.springframework.util.StringUtils;
027
028/**
029 * Abstract implementation of the {@link Errors} interface. Provides common
030 * access to evaluated errors; however, does not define concrete management
031 * of {@link ObjectError ObjectErrors} and {@link FieldError FieldErrors}.
032 *
033 * @author Juergen Hoeller
034 * @author Rossen Stoyanchev
035 * @since 2.5.3
036 */
037@SuppressWarnings("serial")
038public abstract class AbstractErrors implements Errors, Serializable {
039
040        private String nestedPath = "";
041
042        private final Stack<String> nestedPathStack = new Stack<String>();
043
044
045        @Override
046        public void setNestedPath(String nestedPath) {
047                doSetNestedPath(nestedPath);
048                this.nestedPathStack.clear();
049        }
050
051        @Override
052        public String getNestedPath() {
053                return this.nestedPath;
054        }
055
056        @Override
057        public void pushNestedPath(String subPath) {
058                this.nestedPathStack.push(getNestedPath());
059                doSetNestedPath(getNestedPath() + subPath);
060        }
061
062        @Override
063        public void popNestedPath() throws IllegalArgumentException {
064                try {
065                        String formerNestedPath = this.nestedPathStack.pop();
066                        doSetNestedPath(formerNestedPath);
067                }
068                catch (EmptyStackException ex) {
069                        throw new IllegalStateException("Cannot pop nested path: no nested path on stack");
070                }
071        }
072
073        /**
074         * Actually set the nested path.
075         * Delegated to by setNestedPath and pushNestedPath.
076         */
077        protected void doSetNestedPath(String nestedPath) {
078                if (nestedPath == null) {
079                        nestedPath = "";
080                }
081                nestedPath = canonicalFieldName(nestedPath);
082                if (nestedPath.length() > 0 && !nestedPath.endsWith(Errors.NESTED_PATH_SEPARATOR)) {
083                        nestedPath += Errors.NESTED_PATH_SEPARATOR;
084                }
085                this.nestedPath = nestedPath;
086        }
087
088        /**
089         * Transform the given field into its full path,
090         * regarding the nested path of this instance.
091         */
092        protected String fixedField(String field) {
093                if (StringUtils.hasLength(field)) {
094                        return getNestedPath() + canonicalFieldName(field);
095                }
096                else {
097                        String path = getNestedPath();
098                        return (path.endsWith(Errors.NESTED_PATH_SEPARATOR) ?
099                                        path.substring(0, path.length() - NESTED_PATH_SEPARATOR.length()) : path);
100                }
101        }
102
103        /**
104         * Determine the canonical field name for the given field.
105         * <p>The default implementation simply returns the field name as-is.
106         * @param field the original field name
107         * @return the canonical field name
108         */
109        protected String canonicalFieldName(String field) {
110                return field;
111        }
112
113
114        @Override
115        public void reject(String errorCode) {
116                reject(errorCode, null, null);
117        }
118
119        @Override
120        public void reject(String errorCode, String defaultMessage) {
121                reject(errorCode, null, defaultMessage);
122        }
123
124        @Override
125        public void rejectValue(String field, String errorCode) {
126                rejectValue(field, errorCode, null, null);
127        }
128
129        @Override
130        public void rejectValue(String field, String errorCode, String defaultMessage) {
131                rejectValue(field, errorCode, null, defaultMessage);
132        }
133
134
135        @Override
136        public boolean hasErrors() {
137                return !getAllErrors().isEmpty();
138        }
139
140        @Override
141        public int getErrorCount() {
142                return getAllErrors().size();
143        }
144
145        @Override
146        public List<ObjectError> getAllErrors() {
147                List<ObjectError> result = new LinkedList<ObjectError>();
148                result.addAll(getGlobalErrors());
149                result.addAll(getFieldErrors());
150                return Collections.unmodifiableList(result);
151        }
152
153        @Override
154        public boolean hasGlobalErrors() {
155                return (getGlobalErrorCount() > 0);
156        }
157
158        @Override
159        public int getGlobalErrorCount() {
160                return getGlobalErrors().size();
161        }
162
163        @Override
164        public ObjectError getGlobalError() {
165                List<ObjectError> globalErrors = getGlobalErrors();
166                return (!globalErrors.isEmpty() ? globalErrors.get(0) : null);
167        }
168
169        @Override
170        public boolean hasFieldErrors() {
171                return (getFieldErrorCount() > 0);
172        }
173
174        @Override
175        public int getFieldErrorCount() {
176                return getFieldErrors().size();
177        }
178
179        @Override
180        public FieldError getFieldError() {
181                List<FieldError> fieldErrors = getFieldErrors();
182                return (!fieldErrors.isEmpty() ? fieldErrors.get(0) : null);
183        }
184
185        @Override
186        public boolean hasFieldErrors(String field) {
187                return (getFieldErrorCount(field) > 0);
188        }
189
190        @Override
191        public int getFieldErrorCount(String field) {
192                return getFieldErrors(field).size();
193        }
194
195        @Override
196        public List<FieldError> getFieldErrors(String field) {
197                List<FieldError> fieldErrors = getFieldErrors();
198                List<FieldError> result = new LinkedList<FieldError>();
199                String fixedField = fixedField(field);
200                for (FieldError error : fieldErrors) {
201                        if (isMatchingFieldError(fixedField, error)) {
202                                result.add(error);
203                        }
204                }
205                return Collections.unmodifiableList(result);
206        }
207
208        @Override
209        public FieldError getFieldError(String field) {
210                List<FieldError> fieldErrors = getFieldErrors(field);
211                return (!fieldErrors.isEmpty() ? fieldErrors.get(0) : null);
212        }
213
214        @Override
215        public Class<?> getFieldType(String field) {
216                Object value = getFieldValue(field);
217                return (value != null ? value.getClass() : null);
218        }
219
220        /**
221         * Check whether the given FieldError matches the given field.
222         * @param field the field that we are looking up FieldErrors for
223         * @param fieldError the candidate FieldError
224         * @return whether the FieldError matches the given field
225         */
226        protected boolean isMatchingFieldError(String field, FieldError fieldError) {
227                if (field.equals(fieldError.getField())) {
228                        return true;
229                }
230                // Optimization: use charAt and regionMatches instead of endsWith and startsWith (SPR-11304)
231                int endIndex = field.length() - 1;
232                return (endIndex >= 0 && field.charAt(endIndex) == '*' &&
233                                (endIndex == 0 || field.regionMatches(0, fieldError.getField(), 0, endIndex)));
234        }
235
236
237        @Override
238        public String toString() {
239                StringBuilder sb = new StringBuilder(getClass().getName());
240                sb.append(": ").append(getErrorCount()).append(" errors");
241                for (ObjectError error : getAllErrors()) {
242                        sb.append('\n').append(error);
243                }
244                return sb.toString();
245        }
246
247}