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