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}