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