001: /*
002: * Copyright 2004-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: package org.springframework.binding.convert.support;
017:
018: import java.util.Arrays;
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.Map;
025: import java.util.Set;
026:
027: import org.springframework.binding.convert.ConversionException;
028: import org.springframework.binding.convert.ConversionExecutor;
029: import org.springframework.binding.convert.ConversionService;
030: import org.springframework.binding.convert.Converter;
031: import org.springframework.util.Assert;
032: import org.springframework.util.ClassUtils;
033: import org.springframework.util.StringUtils;
034:
035: /**
036: * Base implementation of a conversion service. Initially empty, e.g. no converters
037: * are registered by default.
038: *
039: * @author Keith Donald
040: */
041: public class GenericConversionService implements ConversionService {
042:
043: /**
044: * An indexed map of converters. Each entry key is a source class that can
045: * be converted from, and each entry value is a map of target classes that
046: * can be convertered to, ultimately mapping to a specific converter that
047: * can perform the source->target conversion.
048: */
049: private Map sourceClassConverters = new HashMap();
050:
051: /**
052: * A map of string aliases to convertible classes. Allows lookup of
053: * converters by alias.
054: */
055: private Map aliasMap = new HashMap();
056:
057: /**
058: * An optional parent conversion service.
059: */
060: private ConversionService parent;
061:
062: /**
063: * Returns the parent of this conversion service. Could be null.
064: */
065: public ConversionService getParent() {
066: return parent;
067: }
068:
069: /**
070: * Set the parent of this conversion service. This is optional.
071: */
072: public void setParent(ConversionService parent) {
073: this .parent = parent;
074: }
075:
076: /**
077: * Add given converter to this conversion service. If the converter is
078: * {@link ConversionServiceAware}, it will get the conversion service
079: * injected.
080: */
081: public void addConverter(Converter converter) {
082: Class[] sourceClasses = converter.getSourceClasses();
083: Class[] targetClasses = converter.getTargetClasses();
084: for (int i = 0; i < sourceClasses.length; i++) {
085: Class sourceClass = sourceClasses[i];
086: Map sourceMap = (Map) sourceClassConverters
087: .get(sourceClass);
088: if (sourceMap == null) {
089: sourceMap = new HashMap();
090: sourceClassConverters.put(sourceClass, sourceMap);
091: }
092: for (int j = 0; j < targetClasses.length; j++) {
093: Class targetClass = targetClasses[j];
094: sourceMap.put(targetClass, converter);
095: }
096: }
097: if (converter instanceof ConversionServiceAware) {
098: ((ConversionServiceAware) converter)
099: .setConversionService(this );
100: }
101: }
102:
103: /**
104: * Add all given converters. If the converters are
105: * {@link ConversionServiceAware}, they will get the conversion service
106: * injected.
107: */
108: public void addConverters(Converter[] converters) {
109: for (int i = 0; i < converters.length; i++) {
110: addConverter(converters[i]);
111: }
112: }
113:
114: /**
115: * Add given converter with an alias to the conversion service. If the
116: * converter is {@link ConversionServiceAware}, it will get the conversion
117: * service injected.
118: */
119: public void addConverter(Converter converter, String alias) {
120: aliasMap.put(alias, converter);
121: addConverter(converter);
122: }
123:
124: /**
125: * Add an alias for given target type.
126: */
127: public void addAlias(String alias, Class targetType) {
128: aliasMap.put(alias, targetType);
129: }
130:
131: /**
132: * Generate a conventions based alias for given target type. For instance,
133: * "java.lang.Boolean" will get the "boolean" alias.
134: */
135: public void addDefaultAlias(Class targetType) {
136: addAlias(StringUtils.uncapitalize(ClassUtils
137: .getShortName(targetType)), targetType);
138: }
139:
140: public ConversionExecutor getConversionExecutor(Class sourceClass,
141: Class targetClass) throws ConversionException {
142: Assert.notNull(sourceClass,
143: "The source class to convert from is required");
144: Assert.notNull(targetClass,
145: "The target class to convert to is required");
146: if (this .sourceClassConverters == null
147: || this .sourceClassConverters.isEmpty()) {
148: throw new IllegalStateException(
149: "No converters have been added to this service's registry");
150: }
151: if (targetClass.isAssignableFrom(sourceClass)) {
152: return new ConversionExecutor(sourceClass, targetClass,
153: new NoOpConverter(sourceClass, targetClass));
154: }
155: Map sourceTargetConverters = findConvertersForSource(sourceClass);
156: Converter converter = findTargetConverter(
157: sourceTargetConverters, targetClass);
158: if (converter != null) {
159: // we found a converter
160: return new ConversionExecutor(sourceClass, targetClass,
161: converter);
162: } else {
163: if (parent != null) {
164: // try the parent
165: return parent.getConversionExecutor(sourceClass,
166: targetClass);
167: } else {
168: throw new ConversionException(sourceClass, targetClass,
169: "No converter registered to convert from sourceClass '"
170: + sourceClass + "' to target class '"
171: + targetClass + "'");
172: }
173: }
174: }
175:
176: public ConversionExecutor getConversionExecutorByTargetAlias(
177: Class sourceClass, String alias)
178: throws IllegalArgumentException {
179: Assert.notNull(sourceClass,
180: "The source class to convert from is required");
181: Assert
182: .hasText(
183: alias,
184: "The target alias is required and must either be a type alias (e.g 'boolean') "
185: + "or a generic converter alias (e.g. 'bean') ");
186: Object targetType = aliasMap.get(alias);
187: if (targetType == null) {
188: if (parent != null) {
189: // try the parent
190: return parent.getConversionExecutorByTargetAlias(
191: sourceClass, alias);
192: } else {
193: // not aliased
194: return null;
195: }
196: } else if (targetType instanceof Class) {
197: return getConversionExecutor(sourceClass,
198: (Class) targetType);
199: } else {
200: Assert.isInstanceOf(Converter.class, targetType,
201: "Not a converter: ");
202: Converter conv = (Converter) targetType;
203: return new ConversionExecutor(sourceClass, Object.class,
204: conv);
205: }
206: }
207:
208: public ConversionExecutor[] getConversionExecutorsForSource(
209: Class sourceClass) {
210: Assert.notNull(sourceClass,
211: "The source class to convert from is required");
212: Map sourceTargetConverters = findConvertersForSource(sourceClass);
213: if (sourceTargetConverters.isEmpty()) {
214: if (parent != null) {
215: // use the parent
216: return parent
217: .getConversionExecutorsForSource(sourceClass);
218: } else {
219: // no converters for source class
220: return new ConversionExecutor[0];
221: }
222: } else {
223: Set executors = new HashSet();
224: if (parent != null) {
225: executors.addAll(Arrays.asList(parent
226: .getConversionExecutorsForSource(sourceClass)));
227: }
228: Iterator it = sourceTargetConverters.entrySet().iterator();
229: while (it.hasNext()) {
230: Map.Entry entry = (Map.Entry) it.next();
231: executors.add(new ConversionExecutor(sourceClass,
232: (Class) entry.getKey(), (Converter) entry
233: .getValue()));
234: }
235: return (ConversionExecutor[]) executors
236: .toArray(new ConversionExecutor[executors.size()]);
237: }
238: }
239:
240: public Class getClassByAlias(String alias) {
241: Assert
242: .hasText(alias,
243: "The alias is required and must be a type alias (e.g 'boolean')");
244: Object clazz = aliasMap.get(alias);
245: if (clazz != null) {
246: Assert.isInstanceOf(Class.class, clazz,
247: "Not a Class alias '" + alias + "': ");
248: return (Class) clazz;
249: } else {
250: if (parent != null) {
251: // try parent service
252: return parent.getClassByAlias(alias);
253: } else {
254: // alias does not index a class, return null
255: return null;
256: }
257: }
258: }
259:
260: // internal helpers
261:
262: private Map findConvertersForSource(Class sourceClass) {
263: LinkedList classQueue = new LinkedList();
264: classQueue.addFirst(sourceClass);
265: while (!classQueue.isEmpty()) {
266: sourceClass = (Class) classQueue.removeLast();
267: Map sourceTargetConverters = (Map) sourceClassConverters
268: .get(sourceClass);
269: if (sourceTargetConverters != null
270: && !sourceTargetConverters.isEmpty()) {
271: return sourceTargetConverters;
272: }
273: if (!sourceClass.isInterface()
274: && (sourceClass.getSuperclass() != null)) {
275: classQueue.addFirst(sourceClass.getSuperclass());
276: }
277: // queue up source class's implemented interfaces.
278: Class[] interfaces = sourceClass.getInterfaces();
279: for (int i = 0; i < interfaces.length; i++) {
280: classQueue.addFirst(interfaces[i]);
281: }
282: }
283: return Collections.EMPTY_MAP;
284: }
285:
286: private Converter findTargetConverter(Map sourceTargetConverters,
287: Class targetClass) {
288: LinkedList classQueue = new LinkedList();
289: classQueue.addFirst(targetClass);
290: while (!classQueue.isEmpty()) {
291: targetClass = (Class) classQueue.removeLast();
292: Converter converter = (Converter) sourceTargetConverters
293: .get(targetClass);
294: if (converter != null) {
295: return converter;
296: }
297: if (!targetClass.isInterface()
298: && (targetClass.getSuperclass() != null)) {
299: classQueue.addFirst(targetClass.getSuperclass());
300: }
301: // queue up target class's implemented interfaces.
302: Class[] interfaces = targetClass.getInterfaces();
303: for (int i = 0; i < interfaces.length; i++) {
304: classQueue.addFirst(interfaces[i]);
305: }
306: }
307: return null;
308: }
309:
310: // subclassing support
311:
312: /**
313: * Returns an indexed map of converters. Each entry key is a source class that
314: * can be converted from, and each entry value is a map of target classes that
315: * can be convertered to, ultimately mapping to a specific converter that can
316: * perform the source->target conversion.
317: */
318: protected Map getSourceClassConverters() {
319: return sourceClassConverters;
320: }
321:
322: /**
323: * Returns a map of known aliases. Each entry key is a String alias and the
324: * associated value is either a target class or a converter.
325: */
326: protected Map getAliasMap() {
327: return aliasMap;
328: }
329: }
|