From d9f5a7a6c86123f8cfc3b02c4b3b44d73c79b3f4 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 9 Mar 2009 02:28:22 +0000 Subject: [PATCH] custom converters --- .../service/GenericConversionService.java | 221 ++++++++++++-- .../SuperTwoWayConverterConverter.java | 11 +- .../GenericConversionServiceTests.java | 271 +++++++++++++++++- 3 files changed, 480 insertions(+), 23 deletions(-) rename org.springframework.core/src/main/java/org/springframework/core/convert/{converter => service}/SuperTwoWayConverterConverter.java (61%) diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java index 62cdd078b94..57582d99916 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/GenericConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2009 the original author or authors. + * Copyright 2004-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ import org.springframework.util.Assert; /** * Base implementation of a conversion service. Initially empty, e.g. no converters are registered by default. * - * TODO custom converters TODO handle case where no Converter/SuperConverter generic param info is available + * TODO auto-conversion of generic collection elements * * @author Keith Donald */ @@ -54,11 +54,17 @@ public class GenericConversionService implements ConversionService { private final Map sourceClassConverters = new HashMap(); /** - * An indexed map of Converters. Each Map.Entry key is a source class (S) that can be converted from. Each Map.Entry - * value is a Map that defines the targetClass-to-Converter mappings for that source. + * An indexed map of SuperConverters. Each Map.Entry key is a source class (S) that can be converted from. Each + * Map.Entry value is a Map that defines the targetClass-to-SuperConverter mappings for that source. */ private final Map sourceClassSuperConverters = new HashMap(); + /** + * A map of custom converters. Custom converters are assigned a unique identifier that can be used to lookup the + * converter. This allows multiple converters for the same source->target class to be registered. + */ + private final Map customConverters = new HashMap(); + /** * Indexes classes by well-known aliases. */ @@ -88,7 +94,7 @@ public class GenericConversionService implements ConversionService { * @param converter the converter to register */ public void addConverter(Converter converter) { - List typeInfo = getTypeInfo(converter); + List typeInfo = getRequiredTypeInfo(converter); Class sourceClass = (Class) typeInfo.get(0); Class targetClass = (Class) typeInfo.get(1); // index forward @@ -104,7 +110,7 @@ public class GenericConversionService implements ConversionService { * @param converter the super converter to register */ public void addConverter(SuperConverter converter) { - List typeInfo = getTypeInfo(converter); + List typeInfo = getRequiredTypeInfo(converter); Class sourceClass = (Class) typeInfo.get(0); Class targetClass = (Class) typeInfo.get(1); // index forward @@ -117,6 +123,31 @@ public class GenericConversionService implements ConversionService { } } + /** + * Register the converter as a custom converter with this conversion service. + * @param id the id to assign the converter + * @param converter the converter to use a custom converter + */ + public void addConverter(String id, Converter converter) { + customConverters.put(id, converter); + } + + /** + * Adapts a {@link SuperTwoWayConverter} that converts between BS and BT class hierarchies to a {@link Converter} + * that converts between the specific BS/BT sub types S and T. + * + * TODO - I think this is going to force indexing on a getSourceClass/getTargetclass prop instead generic args + * + * @param sourceClass the source class S to convert from, which must be equal or extend BS + * @param targetClass the target type T to convert to, which must equal or extend BT + * @param converter the super two way converter + * @return a converter that converts from S to T by delegating to the super converter + */ + public static Converter converterFor(Class sourceClass, Class targetClass, + SuperTwoWayConverter converter) { + return new SuperTwoWayConverterConverter(converter, sourceClass, targetClass); + } + /** * Add a convenient alias for the target type. {@link #getClassForAlias(String)} can then be used to lookup the type * given the alias. @@ -126,16 +157,16 @@ public class GenericConversionService implements ConversionService { aliasMap.put(alias, targetType); } + // implementing ConversionService + public Object executeConversion(Object source, Class targetClass) throws ConversionExecutorNotFoundException, ConversionException { - ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass); - return conversionExecutor.execute(source); + return getConversionExecutor(source.getClass(), targetClass).execute(source); } public Object executeConversion(String converterId, Object source, Class targetClass) throws ConversionExecutorNotFoundException, ConversionException { - ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass); - return conversionExecutor.execute(source); + return getConversionExecutor(converterId, source.getClass(), targetClass).execute(source); } public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) @@ -188,9 +219,156 @@ public class GenericConversionService implements ConversionService { } } - public ConversionExecutor getConversionExecutor(String converterId, Class sourceClass, Class targetClass) + public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass) throws ConversionExecutorNotFoundException { - throw new UnsupportedOperationException("Not yet implemented"); + Assert.hasText(id, "The id of the custom converter is required"); + Assert.notNull(sourceClass, "The source class to convert from is required"); + Assert.notNull(targetClass, "The target class to convert to is required"); + Converter converter = (Converter) customConverters.get(id); + if (converter == null) { + if (parent != null) { + return parent.getConversionExecutor(id, sourceClass, targetClass); + } else { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "No custom Converter found with id '" + id + "' for converting from sourceClass [" + + sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]"); + } + } + sourceClass = convertToWrapperClassIfNecessary(sourceClass); + targetClass = convertToWrapperClassIfNecessary(targetClass); + // TODO Not optimal getting this each time + List typeInfo = getRequiredTypeInfo(converter); + Class converterSourceClass = (Class) typeInfo.get(0); + Class converterTargetClass = (Class) typeInfo.get(1); + if (sourceClass.isArray()) { + Class sourceComponentType = sourceClass.getComponentType(); + if (targetClass.isArray()) { + Class targetComponentType = targetClass.getComponentType(); + if (converterSourceClass.isAssignableFrom(sourceComponentType)) { + if (!converterTargetClass.equals(targetComponentType)) { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + + "' cannot convert from an array storing elements of type [" + + sourceComponentType.getName() + "] to an array of storing elements of type [" + + targetComponentType.getName() + "]"); + } + ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, + targetComponentType, converter); + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToArray( + elementConverter)); + } else if (converterTargetClass.isAssignableFrom(sourceComponentType)) { + if (!converterSourceClass.equals(targetComponentType)) { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + + "' cannot convert from an array storing elements of type [" + + sourceComponentType.getName() + "] to an array of storing elements of type [" + + targetComponentType.getName() + "]"); + } + ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, + targetComponentType, new ReverseConverter(converter)); + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToArray( + elementConverter)); + } else { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + + "' cannot convert from an array storing elements of type [" + + sourceComponentType.getName() + "] to an array storing elements of type [" + + targetComponentType.getName() + "]"); + } + } else if (Collection.class.isAssignableFrom(targetClass)) { + if (!targetClass.isInterface() && Modifier.isAbstract(targetClass.getModifiers())) { + throw new IllegalArgumentException("Conversion target class [" + targetClass.getName() + + "] is invalid; cannot convert to abstract collection types--" + + "request an interface or concrete implementation instead"); + } + if (converterSourceClass.isAssignableFrom(sourceComponentType)) { + // type erasure has prevented us from getting the concrete type, this is best we can do for now + ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, + converterTargetClass, converter); + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToCollection( + elementConverter)); + } else if (converterTargetClass.isAssignableFrom(sourceComponentType)) { + ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType, + converterSourceClass, new ReverseConverter(converter)); + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToCollection( + elementConverter)); + } else { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + + "' cannot convert from array an storing elements type [" + + sourceComponentType.getName() + "] to a collection of type [" + + targetClass.getName() + "]"); + } + } + } + if (targetClass.isArray()) { + Class targetComponentType = targetClass.getComponentType(); + if (Collection.class.isAssignableFrom(sourceClass)) { + // type erasure limits us here as well + if (converterTargetClass.equals(targetComponentType)) { + ConversionExecutor elementConverter = new StaticConversionExecutor(converterSourceClass, + targetComponentType, converter); + SuperConverter collectionToArray = new ReverseSuperConverter( + new ArrayToCollection(elementConverter)); + return new StaticSuperConversionExecutor(sourceClass, targetClass, collectionToArray); + } else if (converterSourceClass.equals(targetComponentType)) { + ConversionExecutor elementConverter = new StaticConversionExecutor(converterTargetClass, + targetComponentType, new ReverseConverter(converter)); + SuperConverter collectionToArray = new ReverseSuperConverter( + new ArrayToCollection(elementConverter)); + return new StaticSuperConversionExecutor(sourceClass, targetClass, collectionToArray); + } else { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + "' cannot convert from collection of type [" + + sourceClass.getName() + "] to an array storing elements of type [" + + targetComponentType.getName() + "]"); + } + } else { + if (converterSourceClass.isAssignableFrom(sourceClass)) { + if (!converterTargetClass.equals(targetComponentType)) { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + "' cannot convert from sourceClass [" + + sourceClass.getName() + "] to array holding elements of type [" + + targetComponentType.getName() + "]"); + } + ConversionExecutor elementConverter = new StaticConversionExecutor(sourceClass, + targetComponentType, converter); + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ObjectToArray( + elementConverter)); + } else if (converterTargetClass.isAssignableFrom(sourceClass)) { + if (!converterSourceClass.equals(targetComponentType)) { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, + "Custom Converter with id '" + id + "' cannot convert from sourceClass [" + + sourceClass.getName() + "] to array holding elements of type [" + + targetComponentType.getName() + "]"); + } + ConversionExecutor elementConverter = new StaticConversionExecutor(sourceClass, + targetComponentType, new ReverseConverter(converter)); + return new StaticSuperConversionExecutor(sourceClass, targetClass, new ObjectToArray( + elementConverter)); + } + } + } + // TODO look to factor some of this duplicated code here and above out a bit + if (converterSourceClass.isAssignableFrom(sourceClass)) { + if (!converterTargetClass.equals(targetClass)) { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom Converter with id '" + + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass [" + + targetClass.getName() + "]"); + } + return new StaticConversionExecutor(sourceClass, targetClass, converter); + } else if (converterTargetClass.isAssignableFrom(sourceClass)) { + if (!converterSourceClass.equals(targetClass)) { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom Converter with id '" + + id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass [" + + targetClass.getName() + "]"); + } + return new StaticConversionExecutor(sourceClass, targetClass, new ReverseConverter(converter)); + } else { + throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom Converter with id '" + id + + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass [" + + targetClass.getName() + "]"); + } } public Class getClassForAlias(String name) throws IllegalArgumentException { @@ -208,25 +386,32 @@ public class GenericConversionService implements ConversionService { // internal helpers - private List getTypeInfo(Object converter) { + private List getRequiredTypeInfo(Object converter) { List typeInfo = new ArrayList(2); Class classToIntrospect = converter.getClass(); while (classToIntrospect != null) { - Type[] genericInterfaces = converter.getClass().getGenericInterfaces(); + Type[] genericInterfaces = classToIntrospect.getGenericInterfaces(); for (Type genericInterface : genericInterfaces) { if (genericInterface instanceof ParameterizedType) { ParameterizedType parameterizedInterface = (ParameterizedType) genericInterface; if (Converter.class.equals(parameterizedInterface.getRawType()) || SuperConverter.class.isAssignableFrom((Class) parameterizedInterface.getRawType())) { - Type s = parameterizedInterface.getActualTypeArguments()[0]; - Type t = parameterizedInterface.getActualTypeArguments()[1]; + Class s = getParameterClass(parameterizedInterface.getActualTypeArguments()[0], converter + .getClass()); + Class t = getParameterClass(parameterizedInterface.getActualTypeArguments()[1], converter + .getClass()); typeInfo.add(getParameterClass(s, converter.getClass())); typeInfo.add(getParameterClass(t, converter.getClass())); + break; } } } classToIntrospect = classToIntrospect.getSuperclass(); } + if (typeInfo.size() != 2) { + throw new IllegalArgumentException("Unable to extract source and target class arguments from Converter [" + + converter.getClass().getName() + "]; does the Converter specify the generic types?"); + } return typeInfo; } @@ -237,8 +422,8 @@ public class GenericConversionService implements ConversionService { if (parameterType instanceof Class) { return (Class) parameterType; } - // when would this happen? - return null; + throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType + + "] on Converter [" + converterClass.getName() + "]"); } private Map getSourceMap(Class sourceClass) { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java b/org.springframework.core/src/main/java/org/springframework/core/convert/service/SuperTwoWayConverterConverter.java similarity index 61% rename from org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java rename to org.springframework.core/src/main/java/org/springframework/core/convert/service/SuperTwoWayConverterConverter.java index 0fe221038a3..7bf4091db9a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/converter/SuperTwoWayConverterConverter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/service/SuperTwoWayConverterConverter.java @@ -1,11 +1,15 @@ -package org.springframework.core.convert.converter; +package org.springframework.core.convert.service; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.SuperConverter; +import org.springframework.core.convert.converter.SuperTwoWayConverter; /** * Adapts a {@link SuperTwoWayConverter} to the {@link Converter} interface in a type safe way. This adapter is useful * for applying more general {@link SuperConverter} logic to a specific source/target class pair. */ @SuppressWarnings("unchecked") -public class SuperTwoWayConverterConverter implements Converter { +class SuperTwoWayConverterConverter implements Converter { private SuperTwoWayConverter superConverter; @@ -13,8 +17,7 @@ public class SuperTwoWayConverterConverter implements Converter { private Class targetClass; - public SuperTwoWayConverterConverter(SuperTwoWayConverter superConverter, - Class sourceClass, Class targetClass) { + public SuperTwoWayConverterConverter(SuperTwoWayConverter superConverter, Class sourceClass, Class targetClass) { this.superConverter = superConverter; this.sourceClass = sourceClass; this.targetClass = targetClass; diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java index e62ecc98a05..cfa499f7f0f 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/service/GenericConversionServiceTests.java @@ -1,5 +1,21 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.core.convert.service; +import java.security.Principal; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; @@ -8,6 +24,7 @@ import java.util.List; import junit.framework.TestCase; +import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionExecutionException; import org.springframework.core.convert.ConversionExecutor; import org.springframework.core.convert.ConversionExecutorNotFoundException; @@ -15,7 +32,6 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.NumberToNumber; import org.springframework.core.convert.converter.StringToEnum; import org.springframework.core.convert.converter.StringToInteger; -import org.springframework.core.convert.service.GenericConversionService; public class GenericConversionServiceTests extends TestCase { @@ -250,4 +266,257 @@ public class GenericConversionServiceTests extends TestCase { } } + + public void testAddConverterNoSourceTargetClassInfoAvailable() { + try { + service.addConverter(new Converter() { + public Object convert(Object source) throws Exception { + return source; + } + + public Object convertBack(Object target) throws Exception { + return target; + } + }); + fail("Should have failed"); + } catch (IllegalArgumentException e) { + + } + } + + public void testCustomConverterConversionForwardIndex() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", String.class, Principal.class); + assertEquals("keith", ((Principal) executor.execute("keith")).getName()); + } + + public void testCustomConverterConversionReverseIndex() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", Principal.class, String.class); + assertEquals("keith", executor.execute(new Principal() { + public String getName() { + return "keith"; + } + })); + } + + public void testCustomConverterConversionForSameType() { + service.addConverter("trimmer", new Trimmer()); + ConversionExecutor executor = service.getConversionExecutor("trimmer", String.class, String.class); + assertEquals("a string", executor.execute("a string ")); + } + + public void testCustomConverterLookupNotCompatibleSource() { + service.addConverter("trimmer", new Trimmer()); + try { + service.getConversionExecutor("trimmer", Object.class, String.class); + fail("Should have failed"); + } catch (ConversionException e) { + + } + } + + public void testCustomConverterLookupNotCompatibleTarget() { + service.addConverter("trimmer", new Trimmer()); + try { + service.getConversionExecutor("trimmer", String.class, Object.class); + } catch (ConversionException e) { + + } + } + + public void testCustomConverterLookupNotCompatibleTargetReverse() { + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Principal.class, Integer.class); + } catch (ConversionException e) { + + } + } + + public void testCustomConverterConversionArrayToArray() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", String[].class, Principal[].class); + Principal[] p = (Principal[]) executor.execute(new String[] { "princy1", "princy2" }); + assertEquals("princy1", p[0].getName()); + assertEquals("princy2", p[1].getName()); + } + + public void testCustomConverterConversionArrayToArrayReverse() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", Principal[].class, String[].class); + final Principal princy1 = new Principal() { + public String getName() { + return "princy1"; + } + }; + final Principal princy2 = new Principal() { + public String getName() { + return "princy2"; + } + }; + String[] p = (String[]) executor.execute(new Principal[] { princy1, princy2 }); + assertEquals("princy1", p[0]); + assertEquals("princy2", p[1]); + } + + public void testCustomConverterLookupArrayToArrayBogusSource() { + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Integer[].class, Principal[].class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + } + } + + public void testCustomConverterLookupArrayToArrayBogusTarget() { + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Principal[].class, Integer[].class); + } catch (ConversionExecutorNotFoundException e) { + + } + } + + public void testCustomConverterConversionArrayToCollection() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", String[].class, List.class); + List list = (List) executor.execute(new String[] { "princy1", "princy2" }); + assertEquals("princy1", ((Principal) list.get(0)).getName()); + assertEquals("princy2", ((Principal) list.get(1)).getName()); + } + + public void testCustomConverterConversionArrayToCollectionReverse() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", Principal[].class, List.class); + final Principal princy1 = new Principal() { + public String getName() { + return "princy1"; + } + }; + final Principal princy2 = new Principal() { + public String getName() { + return "princy2"; + } + }; + List p = (List) executor.execute(new Principal[] { princy1, princy2 }); + assertEquals("princy1", p.get(0)); + assertEquals("princy2", p.get(1)); + } + + public void testCustomConverterLookupArrayToCollectionBogusSource() { + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Integer[].class, List.class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + + public void testCustomConverterLookupCollectionToArray() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", List.class, Principal[].class); + List princyList = new ArrayList(); + princyList.add("princy1"); + princyList.add("princy2"); + Principal[] p = (Principal[]) executor.execute(princyList); + assertEquals("princy1", p[0].getName()); + assertEquals("princy2", p[1].getName()); + } + + public void testCustomConverterLookupCollectionToArrayReverse() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", List.class, String[].class); + final Principal princy1 = new Principal() { + public String getName() { + return "princy1"; + } + }; + final Principal princy2 = new Principal() { + public String getName() { + return "princy2"; + } + }; + List princyList = new ArrayList(); + princyList.add(princy1); + princyList.add(princy2); + String[] p = (String[]) executor.execute(princyList); + assertEquals("princy1", p[0]); + assertEquals("princy2", p[1]); + } + + public void testtestCustomConverterLookupCollectionToArrayBogusTarget() { + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", List.class, Integer[].class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + + public void testCustomConverterConversionObjectToArray() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", String.class, Principal[].class); + Principal[] p = (Principal[]) executor.execute("princy1"); + assertEquals("princy1", p[0].getName()); + } + + public void testCustomConverterConversionObjectToArrayReverse() { + service.addConverter("princy", new CustomTwoWayConverter()); + ConversionExecutor executor = service.getConversionExecutor("princy", Principal.class, String[].class); + final Principal princy1 = new Principal() { + public String getName() { + return "princy1"; + } + }; + String[] p = (String[]) executor.execute(princy1); + assertEquals("princy1", p[0]); + } + + public void testCustomConverterLookupObjectToArrayBogusSource() { + service.addConverter("princy", new CustomTwoWayConverter()); + try { + service.getConversionExecutor("princy", Integer.class, Principal[].class); + fail("Should have failed"); + } catch (ConversionExecutorNotFoundException e) { + + } + } + + private static class CustomTwoWayConverter implements Converter { + + public Principal convert(final String source) throws Exception { + return new Principal() { + public String getName() { + return (String) source; + } + }; + } + + public String convertBack(Principal target) throws Exception { + return ((Principal) target).getName(); + } + + } + + private static class Trimmer implements Converter { + + public String convert(String source) throws Exception { + return ((String) source).trim(); + } + + public String convertBack(String target) throws Exception { + throw new UnsupportedOperationException("Will never run"); + } + + } + + public void testSuperTwoWayConverterConverterAdaption() { + // this fails at the moment + //service.addConverter(GenericConversionService.converterFor(String.class, FooEnum.class, new StringToEnum())); + //assertEquals(FooEnum.BAR, service.executeConversion("BAR", FooEnum.class)); + } + } \ No newline at end of file