001/* 002 * Copyright 2002-2015 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.beans; 018 019import java.beans.PropertyEditor; 020import java.io.File; 021import java.io.InputStream; 022import java.io.Reader; 023import java.math.BigDecimal; 024import java.math.BigInteger; 025import java.net.URI; 026import java.net.URL; 027import java.nio.charset.Charset; 028import java.util.Collection; 029import java.util.Currency; 030import java.util.HashMap; 031import java.util.Iterator; 032import java.util.LinkedHashMap; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Locale; 036import java.util.Map; 037import java.util.Properties; 038import java.util.Set; 039import java.util.SortedMap; 040import java.util.SortedSet; 041import java.util.TimeZone; 042import java.util.UUID; 043import java.util.regex.Pattern; 044 045import org.xml.sax.InputSource; 046 047import org.springframework.beans.propertyeditors.ByteArrayPropertyEditor; 048import org.springframework.beans.propertyeditors.CharArrayPropertyEditor; 049import org.springframework.beans.propertyeditors.CharacterEditor; 050import org.springframework.beans.propertyeditors.CharsetEditor; 051import org.springframework.beans.propertyeditors.ClassArrayEditor; 052import org.springframework.beans.propertyeditors.ClassEditor; 053import org.springframework.beans.propertyeditors.CurrencyEditor; 054import org.springframework.beans.propertyeditors.CustomBooleanEditor; 055import org.springframework.beans.propertyeditors.CustomCollectionEditor; 056import org.springframework.beans.propertyeditors.CustomMapEditor; 057import org.springframework.beans.propertyeditors.CustomNumberEditor; 058import org.springframework.beans.propertyeditors.FileEditor; 059import org.springframework.beans.propertyeditors.InputSourceEditor; 060import org.springframework.beans.propertyeditors.InputStreamEditor; 061import org.springframework.beans.propertyeditors.LocaleEditor; 062import org.springframework.beans.propertyeditors.PathEditor; 063import org.springframework.beans.propertyeditors.PatternEditor; 064import org.springframework.beans.propertyeditors.PropertiesEditor; 065import org.springframework.beans.propertyeditors.ReaderEditor; 066import org.springframework.beans.propertyeditors.StringArrayPropertyEditor; 067import org.springframework.beans.propertyeditors.TimeZoneEditor; 068import org.springframework.beans.propertyeditors.URIEditor; 069import org.springframework.beans.propertyeditors.URLEditor; 070import org.springframework.beans.propertyeditors.UUIDEditor; 071import org.springframework.beans.propertyeditors.ZoneIdEditor; 072import org.springframework.core.convert.ConversionService; 073import org.springframework.core.io.Resource; 074import org.springframework.core.io.support.ResourceArrayPropertyEditor; 075import org.springframework.util.ClassUtils; 076 077/** 078 * Base implementation of the {@link PropertyEditorRegistry} interface. 079 * Provides management of default editors and custom editors. 080 * Mainly serves as base class for {@link BeanWrapperImpl}. 081 * 082 * @author Juergen Hoeller 083 * @author Rob Harrop 084 * @since 1.2.6 085 * @see java.beans.PropertyEditorManager 086 * @see java.beans.PropertyEditorSupport#setAsText 087 * @see java.beans.PropertyEditorSupport#setValue 088 */ 089public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { 090 091 private static Class<?> pathClass; 092 093 private static Class<?> zoneIdClass; 094 095 static { 096 ClassLoader cl = PropertyEditorRegistrySupport.class.getClassLoader(); 097 try { 098 pathClass = ClassUtils.forName("java.nio.file.Path", cl); 099 } 100 catch (ClassNotFoundException ex) { 101 // Java 7 Path class not available 102 pathClass = null; 103 } 104 try { 105 zoneIdClass = ClassUtils.forName("java.time.ZoneId", cl); 106 } 107 catch (ClassNotFoundException ex) { 108 // Java 8 ZoneId class not available 109 zoneIdClass = null; 110 } 111 } 112 113 114 private ConversionService conversionService; 115 116 private boolean defaultEditorsActive = false; 117 118 private boolean configValueEditorsActive = false; 119 120 private Map<Class<?>, PropertyEditor> defaultEditors; 121 122 private Map<Class<?>, PropertyEditor> overriddenDefaultEditors; 123 124 private Map<Class<?>, PropertyEditor> customEditors; 125 126 private Map<String, CustomEditorHolder> customEditorsForPath; 127 128 private Map<Class<?>, PropertyEditor> customEditorCache; 129 130 131 /** 132 * Specify a Spring 3.0 ConversionService to use for converting 133 * property values, as an alternative to JavaBeans PropertyEditors. 134 */ 135 public void setConversionService(ConversionService conversionService) { 136 this.conversionService = conversionService; 137 } 138 139 /** 140 * Return the associated ConversionService, if any. 141 */ 142 public ConversionService getConversionService() { 143 return this.conversionService; 144 } 145 146 147 //--------------------------------------------------------------------- 148 // Management of default editors 149 //--------------------------------------------------------------------- 150 151 /** 152 * Activate the default editors for this registry instance, 153 * allowing for lazily registering default editors when needed. 154 */ 155 protected void registerDefaultEditors() { 156 this.defaultEditorsActive = true; 157 } 158 159 /** 160 * Activate config value editors which are only intended for configuration purposes, 161 * such as {@link org.springframework.beans.propertyeditors.StringArrayPropertyEditor}. 162 * <p>Those editors are not registered by default simply because they are in 163 * general inappropriate for data binding purposes. Of course, you may register 164 * them individually in any case, through {@link #registerCustomEditor}. 165 */ 166 public void useConfigValueEditors() { 167 this.configValueEditorsActive = true; 168 } 169 170 /** 171 * Override the default editor for the specified type with the given property editor. 172 * <p>Note that this is different from registering a custom editor in that the editor 173 * semantically still is a default editor. A ConversionService will override such a 174 * default editor, whereas custom editors usually override the ConversionService. 175 * @param requiredType the type of the property 176 * @param propertyEditor the editor to register 177 * @see #registerCustomEditor(Class, PropertyEditor) 178 */ 179 public void overrideDefaultEditor(Class<?> requiredType, PropertyEditor propertyEditor) { 180 if (this.overriddenDefaultEditors == null) { 181 this.overriddenDefaultEditors = new HashMap<Class<?>, PropertyEditor>(); 182 } 183 this.overriddenDefaultEditors.put(requiredType, propertyEditor); 184 } 185 186 /** 187 * Retrieve the default editor for the given property type, if any. 188 * <p>Lazily registers the default editors, if they are active. 189 * @param requiredType type of the property 190 * @return the default editor, or {@code null} if none found 191 * @see #registerDefaultEditors 192 */ 193 public PropertyEditor getDefaultEditor(Class<?> requiredType) { 194 if (!this.defaultEditorsActive) { 195 return null; 196 } 197 if (this.overriddenDefaultEditors != null) { 198 PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType); 199 if (editor != null) { 200 return editor; 201 } 202 } 203 if (this.defaultEditors == null) { 204 createDefaultEditors(); 205 } 206 return this.defaultEditors.get(requiredType); 207 } 208 209 /** 210 * Actually register the default editors for this registry instance. 211 */ 212 private void createDefaultEditors() { 213 this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64); 214 215 // Simple editors, without parameterization capabilities. 216 // The JDK does not contain a default editor for any of these target types. 217 this.defaultEditors.put(Charset.class, new CharsetEditor()); 218 this.defaultEditors.put(Class.class, new ClassEditor()); 219 this.defaultEditors.put(Class[].class, new ClassArrayEditor()); 220 this.defaultEditors.put(Currency.class, new CurrencyEditor()); 221 this.defaultEditors.put(File.class, new FileEditor()); 222 this.defaultEditors.put(InputStream.class, new InputStreamEditor()); 223 this.defaultEditors.put(InputSource.class, new InputSourceEditor()); 224 this.defaultEditors.put(Locale.class, new LocaleEditor()); 225 if (pathClass != null) { 226 this.defaultEditors.put(pathClass, new PathEditor()); 227 } 228 this.defaultEditors.put(Pattern.class, new PatternEditor()); 229 this.defaultEditors.put(Properties.class, new PropertiesEditor()); 230 this.defaultEditors.put(Reader.class, new ReaderEditor()); 231 this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor()); 232 this.defaultEditors.put(TimeZone.class, new TimeZoneEditor()); 233 this.defaultEditors.put(URI.class, new URIEditor()); 234 this.defaultEditors.put(URL.class, new URLEditor()); 235 this.defaultEditors.put(UUID.class, new UUIDEditor()); 236 if (zoneIdClass != null) { 237 this.defaultEditors.put(zoneIdClass, new ZoneIdEditor()); 238 } 239 240 // Default instances of collection editors. 241 // Can be overridden by registering custom instances of those as custom editors. 242 this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class)); 243 this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class)); 244 this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class)); 245 this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class)); 246 this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class)); 247 248 // Default editors for primitive arrays. 249 this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor()); 250 this.defaultEditors.put(char[].class, new CharArrayPropertyEditor()); 251 252 // The JDK does not contain a default editor for char! 253 this.defaultEditors.put(char.class, new CharacterEditor(false)); 254 this.defaultEditors.put(Character.class, new CharacterEditor(true)); 255 256 // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor. 257 this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false)); 258 this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); 259 260 // The JDK does not contain default editors for number wrapper types! 261 // Override JDK primitive number editors with our own CustomNumberEditor. 262 this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false)); 263 this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true)); 264 this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false)); 265 this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true)); 266 this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false)); 267 this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true)); 268 this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false)); 269 this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true)); 270 this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false)); 271 this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true)); 272 this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false)); 273 this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true)); 274 this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true)); 275 this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true)); 276 277 // Only register config value editors if explicitly requested. 278 if (this.configValueEditorsActive) { 279 StringArrayPropertyEditor sae = new StringArrayPropertyEditor(); 280 this.defaultEditors.put(String[].class, sae); 281 this.defaultEditors.put(short[].class, sae); 282 this.defaultEditors.put(int[].class, sae); 283 this.defaultEditors.put(long[].class, sae); 284 } 285 } 286 287 /** 288 * Copy the default editors registered in this instance to the given target registry. 289 * @param target the target registry to copy to 290 */ 291 protected void copyDefaultEditorsTo(PropertyEditorRegistrySupport target) { 292 target.defaultEditorsActive = this.defaultEditorsActive; 293 target.configValueEditorsActive = this.configValueEditorsActive; 294 target.defaultEditors = this.defaultEditors; 295 target.overriddenDefaultEditors = this.overriddenDefaultEditors; 296 } 297 298 299 //--------------------------------------------------------------------- 300 // Management of custom editors 301 //--------------------------------------------------------------------- 302 303 @Override 304 public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) { 305 registerCustomEditor(requiredType, null, propertyEditor); 306 } 307 308 @Override 309 public void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor) { 310 if (requiredType == null && propertyPath == null) { 311 throw new IllegalArgumentException("Either requiredType or propertyPath is required"); 312 } 313 if (propertyPath != null) { 314 if (this.customEditorsForPath == null) { 315 this.customEditorsForPath = new LinkedHashMap<String, CustomEditorHolder>(16); 316 } 317 this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType)); 318 } 319 else { 320 if (this.customEditors == null) { 321 this.customEditors = new LinkedHashMap<Class<?>, PropertyEditor>(16); 322 } 323 this.customEditors.put(requiredType, propertyEditor); 324 this.customEditorCache = null; 325 } 326 } 327 328 @Override 329 public PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath) { 330 Class<?> requiredTypeToUse = requiredType; 331 if (propertyPath != null) { 332 if (this.customEditorsForPath != null) { 333 // Check property-specific editor first. 334 PropertyEditor editor = getCustomEditor(propertyPath, requiredType); 335 if (editor == null) { 336 List<String> strippedPaths = new LinkedList<String>(); 337 addStrippedPropertyPaths(strippedPaths, "", propertyPath); 338 for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editor == null;) { 339 String strippedPath = it.next(); 340 editor = getCustomEditor(strippedPath, requiredType); 341 } 342 } 343 if (editor != null) { 344 return editor; 345 } 346 } 347 if (requiredType == null) { 348 requiredTypeToUse = getPropertyType(propertyPath); 349 } 350 } 351 // No property-specific editor -> check type-specific editor. 352 return getCustomEditor(requiredTypeToUse); 353 } 354 355 /** 356 * Determine whether this registry contains a custom editor 357 * for the specified array/collection element. 358 * @param elementType the target type of the element 359 * (can be {@code null} if not known) 360 * @param propertyPath the property path (typically of the array/collection; 361 * can be {@code null} if not known) 362 * @return whether a matching custom editor has been found 363 */ 364 public boolean hasCustomEditorForElement(Class<?> elementType, String propertyPath) { 365 if (propertyPath != null && this.customEditorsForPath != null) { 366 for (Map.Entry<String, CustomEditorHolder> entry : this.customEditorsForPath.entrySet()) { 367 if (PropertyAccessorUtils.matchesProperty(entry.getKey(), propertyPath)) { 368 if (entry.getValue().getPropertyEditor(elementType) != null) { 369 return true; 370 } 371 } 372 } 373 } 374 // No property-specific editor -> check type-specific editor. 375 return (elementType != null && this.customEditors != null && this.customEditors.containsKey(elementType)); 376 } 377 378 /** 379 * Determine the property type for the given property path. 380 * <p>Called by {@link #findCustomEditor} if no required type has been specified, 381 * to be able to find a type-specific editor even if just given a property path. 382 * <p>The default implementation always returns {@code null}. 383 * BeanWrapperImpl overrides this with the standard {@code getPropertyType} 384 * method as defined by the BeanWrapper interface. 385 * @param propertyPath the property path to determine the type for 386 * @return the type of the property, or {@code null} if not determinable 387 * @see BeanWrapper#getPropertyType(String) 388 */ 389 protected Class<?> getPropertyType(String propertyPath) { 390 return null; 391 } 392 393 /** 394 * Get custom editor that has been registered for the given property. 395 * @param propertyName the property path to look for 396 * @param requiredType the type to look for 397 * @return the custom editor, or {@code null} if none specific for this property 398 */ 399 private PropertyEditor getCustomEditor(String propertyName, Class<?> requiredType) { 400 CustomEditorHolder holder = this.customEditorsForPath.get(propertyName); 401 return (holder != null ? holder.getPropertyEditor(requiredType) : null); 402 } 403 404 /** 405 * Get custom editor for the given type. If no direct match found, 406 * try custom editor for superclass (which will in any case be able 407 * to render a value as String via {@code getAsText}). 408 * @param requiredType the type to look for 409 * @return the custom editor, or {@code null} if none found for this type 410 * @see java.beans.PropertyEditor#getAsText() 411 */ 412 private PropertyEditor getCustomEditor(Class<?> requiredType) { 413 if (requiredType == null || this.customEditors == null) { 414 return null; 415 } 416 // Check directly registered editor for type. 417 PropertyEditor editor = this.customEditors.get(requiredType); 418 if (editor == null) { 419 // Check cached editor for type, registered for superclass or interface. 420 if (this.customEditorCache != null) { 421 editor = this.customEditorCache.get(requiredType); 422 } 423 if (editor == null) { 424 // Find editor for superclass or interface. 425 for (Iterator<Class<?>> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) { 426 Class<?> key = it.next(); 427 if (key.isAssignableFrom(requiredType)) { 428 editor = this.customEditors.get(key); 429 // Cache editor for search type, to avoid the overhead 430 // of repeated assignable-from checks. 431 if (this.customEditorCache == null) { 432 this.customEditorCache = new HashMap<Class<?>, PropertyEditor>(); 433 } 434 this.customEditorCache.put(requiredType, editor); 435 } 436 } 437 } 438 } 439 return editor; 440 } 441 442 /** 443 * Guess the property type of the specified property from the registered 444 * custom editors (provided that they were registered for a specific type). 445 * @param propertyName the name of the property 446 * @return the property type, or {@code null} if not determinable 447 */ 448 protected Class<?> guessPropertyTypeFromEditors(String propertyName) { 449 if (this.customEditorsForPath != null) { 450 CustomEditorHolder editorHolder = this.customEditorsForPath.get(propertyName); 451 if (editorHolder == null) { 452 List<String> strippedPaths = new LinkedList<String>(); 453 addStrippedPropertyPaths(strippedPaths, "", propertyName); 454 for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editorHolder == null;) { 455 String strippedName = it.next(); 456 editorHolder = this.customEditorsForPath.get(strippedName); 457 } 458 } 459 if (editorHolder != null) { 460 return editorHolder.getRegisteredType(); 461 } 462 } 463 return null; 464 } 465 466 /** 467 * Copy the custom editors registered in this instance to the given target registry. 468 * @param target the target registry to copy to 469 * @param nestedProperty the nested property path of the target registry, if any. 470 * If this is non-null, only editors registered for a path below this nested property 471 * will be copied. If this is null, all editors will be copied. 472 */ 473 protected void copyCustomEditorsTo(PropertyEditorRegistry target, String nestedProperty) { 474 String actualPropertyName = 475 (nestedProperty != null ? PropertyAccessorUtils.getPropertyName(nestedProperty) : null); 476 if (this.customEditors != null) { 477 for (Map.Entry<Class<?>, PropertyEditor> entry : this.customEditors.entrySet()) { 478 target.registerCustomEditor(entry.getKey(), entry.getValue()); 479 } 480 } 481 if (this.customEditorsForPath != null) { 482 for (Map.Entry<String, CustomEditorHolder> entry : this.customEditorsForPath.entrySet()) { 483 String editorPath = entry.getKey(); 484 CustomEditorHolder editorHolder = entry.getValue(); 485 if (nestedProperty != null) { 486 int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(editorPath); 487 if (pos != -1) { 488 String editorNestedProperty = editorPath.substring(0, pos); 489 String editorNestedPath = editorPath.substring(pos + 1); 490 if (editorNestedProperty.equals(nestedProperty) || editorNestedProperty.equals(actualPropertyName)) { 491 target.registerCustomEditor( 492 editorHolder.getRegisteredType(), editorNestedPath, editorHolder.getPropertyEditor()); 493 } 494 } 495 } 496 else { 497 target.registerCustomEditor( 498 editorHolder.getRegisteredType(), editorPath, editorHolder.getPropertyEditor()); 499 } 500 } 501 } 502 } 503 504 505 /** 506 * Add property paths with all variations of stripped keys and/or indexes. 507 * Invokes itself recursively with nested paths. 508 * @param strippedPaths the result list to add to 509 * @param nestedPath the current nested path 510 * @param propertyPath the property path to check for keys/indexes to strip 511 */ 512 private void addStrippedPropertyPaths(List<String> strippedPaths, String nestedPath, String propertyPath) { 513 int startIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR); 514 if (startIndex != -1) { 515 int endIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR); 516 if (endIndex != -1) { 517 String prefix = propertyPath.substring(0, startIndex); 518 String key = propertyPath.substring(startIndex, endIndex + 1); 519 String suffix = propertyPath.substring(endIndex + 1); 520 // Strip the first key. 521 strippedPaths.add(nestedPath + prefix + suffix); 522 // Search for further keys to strip, with the first key stripped. 523 addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix); 524 // Search for further keys to strip, with the first key not stripped. 525 addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix); 526 } 527 } 528 } 529 530 531 /** 532 * Holder for a registered custom editor with property name. 533 * Keeps the PropertyEditor itself plus the type it was registered for. 534 */ 535 private static class CustomEditorHolder { 536 537 private final PropertyEditor propertyEditor; 538 539 private final Class<?> registeredType; 540 541 private CustomEditorHolder(PropertyEditor propertyEditor, Class<?> registeredType) { 542 this.propertyEditor = propertyEditor; 543 this.registeredType = registeredType; 544 } 545 546 private PropertyEditor getPropertyEditor() { 547 return this.propertyEditor; 548 } 549 550 private Class<?> getRegisteredType() { 551 return this.registeredType; 552 } 553 554 private PropertyEditor getPropertyEditor(Class<?> requiredType) { 555 // Special case: If no required type specified, which usually only happens for 556 // Collection elements, or required type is not assignable to registered type, 557 // which usually only happens for generic properties of type Object - 558 // then return PropertyEditor if not registered for Collection or array type. 559 // (If not registered for Collection or array, it is assumed to be intended 560 // for elements.) 561 if (this.registeredType == null || 562 (requiredType != null && 563 (ClassUtils.isAssignable(this.registeredType, requiredType) || 564 ClassUtils.isAssignable(requiredType, this.registeredType))) || 565 (requiredType == null && 566 (!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) { 567 return this.propertyEditor; 568 } 569 else { 570 return null; 571 } 572 } 573 } 574 575}