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.propertyeditors;
018:
019: import java.beans.PropertyEditorSupport;
020: import java.lang.reflect.Array;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.Iterator;
024: import java.util.LinkedHashSet;
025: import java.util.List;
026: import java.util.SortedSet;
027: import java.util.TreeSet;
028:
029: /**
030: * Property editor for Collections, converting any source Collection
031: * to a given target Collection type.
032: *
033: * <p>By default registered for Set, SortedSet and List,
034: * to automatically convert any given Collection to one of those
035: * target types if the type does not match the target property.
036: *
037: * @author Juergen Hoeller
038: * @since 1.1.3
039: * @see java.util.Collection
040: * @see java.util.Set
041: * @see java.util.SortedSet
042: * @see java.util.List
043: */
044: public class CustomCollectionEditor extends PropertyEditorSupport {
045:
046: private final Class collectionType;
047:
048: private final boolean nullAsEmptyCollection;
049:
050: /**
051: * Create a new CustomCollectionEditor for the given target type,
052: * keeping an incoming <code>null</code> as-is.
053: * @param collectionType the target type, which needs to be a
054: * sub-interface of Collection or a concrete Collection class
055: * @see java.util.Collection
056: * @see java.util.ArrayList
057: * @see java.util.TreeSet
058: * @see java.util.LinkedHashSet
059: */
060: public CustomCollectionEditor(Class collectionType) {
061: this (collectionType, false);
062: }
063:
064: /**
065: * Create a new CustomCollectionEditor for the given target type.
066: * <p>If the incoming value is of the given type, it will be used as-is.
067: * If it is a different Collection type or an array, it will be converted
068: * to a default implementation of the given Collection type.
069: * If the value is anything else, a target Collection with that single
070: * value will be created.
071: * <p>The default Collection implementations are: ArrayList for List,
072: * TreeSet for SortedSet, and LinkedHashSet for Set.
073: * @param collectionType the target type, which needs to be a
074: * sub-interface of Collection or a concrete Collection class
075: * @param nullAsEmptyCollection whether to convert an incoming <code>null</code>
076: * value to an empty Collection (of the appropriate type)
077: * @see java.util.Collection
078: * @see java.util.ArrayList
079: * @see java.util.TreeSet
080: * @see java.util.LinkedHashSet
081: */
082: public CustomCollectionEditor(Class collectionType,
083: boolean nullAsEmptyCollection) {
084: if (collectionType == null) {
085: throw new IllegalArgumentException(
086: "Collection type is required");
087: }
088: if (!Collection.class.isAssignableFrom(collectionType)) {
089: throw new IllegalArgumentException("Collection type ["
090: + collectionType.getName()
091: + "] does not implement [java.util.Collection]");
092: }
093: this .collectionType = collectionType;
094: this .nullAsEmptyCollection = nullAsEmptyCollection;
095: }
096:
097: /**
098: * Convert the given text value to a Collection with a single element.
099: */
100: public void setAsText(String text) throws IllegalArgumentException {
101: setValue(text);
102: }
103:
104: /**
105: * Convert the given value to a Collection of the target type.
106: */
107: public void setValue(Object value) {
108: if (value == null && this .nullAsEmptyCollection) {
109: super .setValue(createCollection(this .collectionType, 0));
110: } else if (value == null
111: || (this .collectionType.isInstance(value) && !alwaysCreateNewCollection())) {
112: // Use the source value as-is, as it matches the target type.
113: super .setValue(value);
114: } else if (value instanceof Collection) {
115: // Convert Collection elements.
116: Collection source = (Collection) value;
117: Collection target = createCollection(this .collectionType,
118: source.size());
119: for (Iterator it = source.iterator(); it.hasNext();) {
120: target.add(convertElement(it.next()));
121: }
122: super .setValue(target);
123: } else if (value.getClass().isArray()) {
124: // Convert array elements to Collection elements.
125: int length = Array.getLength(value);
126: Collection target = createCollection(this .collectionType,
127: length);
128: for (int i = 0; i < length; i++) {
129: target.add(convertElement(Array.get(value, i)));
130: }
131: super .setValue(target);
132: } else {
133: // A plain value: convert it to a Collection with a single element.
134: Collection target = createCollection(this .collectionType, 1);
135: target.add(convertElement(value));
136: super .setValue(target);
137: }
138: }
139:
140: /**
141: * Create a Collection of the given type, with the given
142: * initial capacity (if supported by the Collection type).
143: * @param collectionType a sub-interface of Collection
144: * @param initialCapacity the initial capacity
145: * @return the new Collection instance
146: */
147: protected Collection createCollection(Class collectionType,
148: int initialCapacity) {
149: if (!collectionType.isInterface()) {
150: try {
151: return (Collection) collectionType.newInstance();
152: } catch (Exception ex) {
153: throw new IllegalArgumentException(
154: "Could not instantiate collection class ["
155: + collectionType.getName() + "]: "
156: + ex.getMessage());
157: }
158: } else if (List.class.equals(collectionType)) {
159: return new ArrayList(initialCapacity);
160: } else if (SortedSet.class.equals(collectionType)) {
161: return new TreeSet();
162: } else {
163: return new LinkedHashSet(initialCapacity);
164: }
165: }
166:
167: /**
168: * Return whether to always create a new Collection,
169: * even if the type of the passed-in Collection already matches.
170: * <p>Default is "false"; can be overridden to enforce creation of a
171: * new Collection, for example to convert elements in any case.
172: * @see #convertElement
173: */
174: protected boolean alwaysCreateNewCollection() {
175: return false;
176: }
177:
178: /**
179: * Hook to convert each encountered Collection/array element.
180: * The default implementation simply returns the passed-in element as-is.
181: * <p>Can be overridden to perform conversion of certain elements,
182: * for example String to Integer if a String array comes in and
183: * should be converted to a Set of Integer objects.
184: * <p>Only called if actually creating a new Collection!
185: * This is by default not the case if the type of the passed-in Collection
186: * already matches. Override {@link #alwaysCreateNewCollection()} to
187: * enforce creating a new Collection in every case.
188: * @param element the source element
189: * @return the element to be used in the target Collection
190: * @see #alwaysCreateNewCollection()
191: */
192: protected Object convertElement(Object element) {
193: return element;
194: }
195:
196: /**
197: * This implementation returns <code>null</code> to indicate that
198: * there is no appropriate text representation.
199: */
200: public String getAsText() {
201: return null;
202: }
203:
204: }
|