diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java index f435233a..66816a66 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/ConversionService.java @@ -31,7 +31,7 @@ public interface ConversionService { * @param sourceClass the source class to convert from * @param targetClass the target class to convert to * @return the executor that can execute instance conversion, never null - * @throws ConversionException an exception occured retrieving a converter for the source-to-target pair + * @throws ConversionException an exception occurred retrieving a converter for the source-to-target pair */ public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionException; diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionExecutorImpl.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionExecutorImpl.java new file mode 100644 index 00000000..898b8efd --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionExecutorImpl.java @@ -0,0 +1,122 @@ +/* + * Copyright 2004-2007 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.binding.convert.support; + +import org.springframework.binding.convert.ConversionContext; +import org.springframework.binding.convert.ConversionException; +import org.springframework.binding.convert.ConversionExecutor; +import org.springframework.binding.convert.Converter; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; + +/** + * A command object that is parameterized with the information necessary to perform a conversion of a source input to a + * target output. + *
+ * Specifically, encapsulates knowledge about how to convert source objects to a specific target type using a specific + * converter. + * + * @author Keith Donald + */ +class ConversionExecutorImpl implements ConversionExecutor { + + /** + * The source value type this executor will attempt to convert from. + */ + private final Class sourceClass; + + /** + * The target value type this executor will attempt to convert to. + */ + private final Class targetClass; + + /** + * The converter that will perform the conversion. + */ + private final Converter converter; + + /** + * Creates a conversion executor. + * @param sourceClass the source type that the converter will convert from + * @param targetClass the target type that the converter will convert to + * @param converter the converter that will perform the conversion + */ + public ConversionExecutorImpl(Class sourceClass, Class targetClass, Converter converter) { + Assert.notNull(sourceClass, "The source class is required"); + Assert.notNull(targetClass, "The target class is required"); + Assert.notNull(converter, "The converter is required"); + this.sourceClass = sourceClass; + this.targetClass = targetClass; + this.converter = converter; + } + + /** + * Returns the source class of conversions performed by this executor. + * @return the source class + */ + public Class getSourceClass() { + return sourceClass; + } + + /** + * Returns the target class of conversions performed by this executor. + * @return the target class + */ + public Class getTargetClass() { + return targetClass; + } + + /** + * Returns the converter that will perform the conversion. + * @return the converter + */ + public Converter getConverter() { + return converter; + } + + public Object execute(Object source) throws ConversionException { + return execute(source, null); + } + + public Object execute(Object source, ConversionContext context) throws ConversionException { + if (targetClass.isInstance(source)) { + // source is already assignment compatible with target class + return source; + } + if (source != null && !sourceClass.isInstance(source)) { + throw new ConversionException(getSourceClass(), source, getTargetClass(), "Source object '" + source + + "' is expected to be an instance of " + getSourceClass()); + } + return converter.convert(source, targetClass, context); + } + + public boolean equals(Object o) { + if (!(o instanceof ConversionExecutorImpl)) { + return false; + } + ConversionExecutorImpl other = (ConversionExecutorImpl) o; + return sourceClass.equals(other.sourceClass) && targetClass.equals(other.targetClass); + } + + public int hashCode() { + return sourceClass.hashCode() + targetClass.hashCode(); + } + + public String toString() { + return new ToStringCreator(this).append("sourceClass", sourceClass).append("targetClass", targetClass) + .toString(); + } +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionServiceAwareConverter.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionServiceAwareConverter.java index fa56d37f..4295a727 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionServiceAwareConverter.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/ConversionServiceAwareConverter.java @@ -15,8 +15,8 @@ */ package org.springframework.binding.convert.support; -import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.expression.Expression; /** diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/ConverterPropertyEditorAdapter.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/ConverterPropertyEditorAdapter.java index 27ed638d..04f43e22 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/support/ConverterPropertyEditorAdapter.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/ConverterPropertyEditorAdapter.java @@ -40,8 +40,10 @@ public class ConverterPropertyEditorAdapter extends PropertyEditorSupport { */ public ConverterPropertyEditorAdapter(ConversionExecutor conversionExecutor) { Assert.notNull(conversionExecutor, "A conversion executor is required"); - Assert.isTrue(conversionExecutor.getSourceClass().equals(String.class), - "A string conversion executor is required"); + if (conversionExecutor.getSourceClass() != null) { + Assert.isTrue(conversionExecutor.getSourceClass().equals(String.class), + "A string conversion executor is required"); + } this.conversionExecutor = conversionExecutor; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/CustomConverterConfigurer.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/CustomConverterConfigurer.java index 32a3a093..5c05ff28 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/support/CustomConverterConfigurer.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/CustomConverterConfigurer.java @@ -21,8 +21,8 @@ import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.convert.ConversionExecutor; import org.springframework.util.Assert; /** diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java index 61aea74b..e2d42b82 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/GenericConversionService.java @@ -135,13 +135,13 @@ public class GenericConversionService implements ConversionService { throw new IllegalStateException("No converters have been added to this service's registry"); } if (targetClass.isAssignableFrom(sourceClass)) { - return new ConversionExecutor(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass)); + return new ConversionExecutorImpl(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass)); } Map sourceTargetConverters = findConvertersForSource(sourceClass); Converter converter = findTargetConverter(sourceTargetConverters, targetClass); if (converter != null) { // we found a converter - return new ConversionExecutor(sourceClass, targetClass, converter); + return new ConversionExecutorImpl(sourceClass, targetClass, converter); } else { if (parent != null) { // try the parent @@ -172,8 +172,8 @@ public class GenericConversionService implements ConversionService { return getConversionExecutor(sourceClass, (Class) targetType); } else { Assert.isInstanceOf(Converter.class, targetType, "Not a converter: "); - Converter conv = (Converter) targetType; - return new ConversionExecutor(sourceClass, Object.class, conv); + Converter converter = (Converter) targetType; + return new ConversionExecutorImpl(sourceClass, Object.class, converter); } } @@ -196,8 +196,8 @@ public class GenericConversionService implements ConversionService { Iterator it = sourceTargetConverters.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); - executors - .add(new ConversionExecutor(sourceClass, (Class) entry.getKey(), (Converter) entry.getValue())); + executors.add(new ConversionExecutorImpl(sourceClass, (Class) entry.getKey(), (Converter) entry + .getValue())); } return (ConversionExecutor[]) executors.toArray(new ConversionExecutor[executors.size()]); } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/RuntimeBindingConversionExecutor.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/RuntimeBindingConversionExecutor.java new file mode 100644 index 00000000..a0ae9ed0 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/RuntimeBindingConversionExecutor.java @@ -0,0 +1,59 @@ +package org.springframework.binding.convert.support; + +import org.springframework.binding.convert.ConversionContext; +import org.springframework.binding.convert.ConversionException; +import org.springframework.binding.convert.ConversionExecutor; +import org.springframework.binding.convert.ConversionService; +import org.springframework.util.Assert; + +/** + * A conversion executor that doesn't resolve its converter until its time to perform a conversion. + * @author Keith Donald + */ +public class RuntimeBindingConversionExecutor implements ConversionExecutor { + + private Class targetClass; + + private ConversionService conversionService; + + /** + * Creates a new runtime binding conversion executor. + * @param targetClass the target type to convert to + * @param conversionService the conversion service to get converters from + */ + public RuntimeBindingConversionExecutor(Class targetClass, ConversionService conversionService) { + Assert.notNull(targetClass, "The target class of the conversion is required"); + Assert.notNull(conversionService, "The conversion service is required"); + this.targetClass = targetClass; + this.conversionService = conversionService; + } + + public Class getSourceClass() { + return null; + } + + public Class getTargetClass() { + return targetClass; + } + + public boolean equals(Object obj) { + if (!(obj instanceof RuntimeBindingConversionExecutor)) { + return false; + } + RuntimeBindingConversionExecutor o = (RuntimeBindingConversionExecutor) obj; + return targetClass.equals(o.targetClass); + } + + public int hashCode() { + return targetClass.hashCode(); + } + + public Object execute(Object source) throws ConversionException { + return execute(source, null); + } + + public Object execute(Object source, ConversionContext context) throws ConversionException { + return conversionService.getConversionExecutor(source.getClass(), targetClass).execute(source); + } + +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java b/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java index d8dce049..f91edadb 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/Mapping.java @@ -23,7 +23,7 @@ import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; /** - * A single mapping definition, encapulating the information neccessary to map the result of evaluating an expression on + * A single mapping definition, encapsulating the information necessary to map the result of evaluating an expression on * a source object to a property on a target object, optionally applying a type conversion during the mapping process. * * @author Keith Donald @@ -69,7 +69,7 @@ public class Mapping implements AttributeMapper { * @param typeConverter a type converter * @param required whether or not this mapping is required */ - protected Mapping(Expression sourceExpression, Expression targetExpression, ConversionExecutor typeConverter, + public Mapping(Expression sourceExpression, Expression targetExpression, ConversionExecutor typeConverter, boolean required) { Assert.notNull(sourceExpression, "The source expression is required"); Assert.notNull(targetExpression, "The target expression is required"); diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java index 6d0cfa6f..cfbb9644 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java @@ -15,9 +15,10 @@ */ package org.springframework.binding.mapping; -import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionService; +import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.support.DefaultConversionService; +import org.springframework.binding.convert.support.RuntimeBindingConversionExecutor; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.support.CollectionAddingExpression; @@ -166,16 +167,14 @@ public class MappingBuilder { targetExpression = sourceExpression; } ConversionExecutor typeConverter = null; - if (sourceType != null) { - Assert.notNull(targetType, "The target type is required when the source type is specified"); - typeConverter = conversionService.getConversionExecutor(sourceType, targetType); - } - Mapping result; - if (required) { - result = new RequiredMapping(sourceExpression, targetExpression, typeConverter); - } else { - result = new Mapping(sourceExpression, targetExpression, typeConverter); + if (targetType != null) { + if (sourceType != null) { + typeConverter = conversionService.getConversionExecutor(sourceType, targetType); + } else { + typeConverter = new RuntimeBindingConversionExecutor(targetType, conversionService); + } } + Mapping result = new Mapping(sourceExpression, targetExpression, typeConverter, required); reset(); return result; } diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/RequiredMapping.java b/spring-binding/src/main/java/org/springframework/binding/mapping/RequiredMapping.java deleted file mode 100644 index 39410065..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/RequiredMapping.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2004-2007 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.binding.mapping; - -import org.springframework.binding.convert.ConversionExecutor; -import org.springframework.binding.expression.Expression; - -/** - * A mapping that is required. - * - * @author Keith Donald - */ -public class RequiredMapping extends Mapping { - - /** - * Creates a required mapping. - * @param sourceExpression the source mapping expression - * @param targetPropertyExpression the target property expression - * @param typeConverter a type converter - */ - public RequiredMapping(Expression sourceExpression, Expression targetPropertyExpression, - ConversionExecutor typeConverter) { - super(sourceExpression, targetPropertyExpression, typeConverter, true); - } -} diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/support/CompositeConversionServiceTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/support/CompositeConversionServiceTests.java index 12b8cd64..6b84c72c 100644 --- a/spring-binding/src/test/java/org/springframework/binding/convert/support/CompositeConversionServiceTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/convert/support/CompositeConversionServiceTests.java @@ -70,10 +70,10 @@ public class CompositeConversionServiceTests extends TestCase { service.getConversionExecutorsForSource(String.class).length); assertEquals(0, service.getConversionExecutorsForSource(Date.class).length); ConversionExecutor[] fromStringConversionExecutors = service.getConversionExecutorsForSource(String.class); - ConversionExecutor booleanConversionExecutor = null; + ConversionExecutorImpl booleanConversionExecutor = null; for (int i = 0; i < fromStringConversionExecutors.length; i++) { - if (fromStringConversionExecutors[i].getConverter() instanceof TextToBoolean) { - booleanConversionExecutor = fromStringConversionExecutors[i]; + if (((ConversionExecutorImpl) fromStringConversionExecutors[i]).getConverter() instanceof TextToBoolean) { + booleanConversionExecutor = (ConversionExecutorImpl) fromStringConversionExecutors[i]; } } assertEquals(Boolean.TRUE, booleanConversionExecutor.execute("ja")); diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/ConversionExecutorTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/support/ConversionExecutorImplTests.java similarity index 76% rename from spring-binding/src/test/java/org/springframework/binding/convert/ConversionExecutorTests.java rename to spring-binding/src/test/java/org/springframework/binding/convert/support/ConversionExecutorImplTests.java index 6d5e5fe4..23d57b95 100644 --- a/spring-binding/src/test/java/org/springframework/binding/convert/ConversionExecutorTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/convert/support/ConversionExecutorImplTests.java @@ -13,23 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.binding.convert; +package org.springframework.binding.convert.support; import java.util.Date; import junit.framework.TestCase; -import org.springframework.binding.convert.support.AbstractConverter; +import org.springframework.binding.convert.ConversionContext; +import org.springframework.binding.convert.ConversionException; /** - * Test case for {@link ConversionExecutor}. + * Test case for {@link ConversionExecutorImpl}. */ -public class ConversionExecutorTests extends TestCase { +public class ConversionExecutorImplTests extends TestCase { - private ConversionExecutor conversionExecutor; + private ConversionExecutorImpl conversionExecutor; protected void setUp() throws Exception { - conversionExecutor = new ConversionExecutor(String.class, Date.class, new TestTextToDate()); + conversionExecutor = new ConversionExecutorImpl(String.class, Date.class, new TestTextToDate()); } public void testTypeConversion() { diff --git a/spring-binding/src/test/java/org/springframework/binding/convert/support/DefaultConversionServiceTests.java b/spring-binding/src/test/java/org/springframework/binding/convert/support/DefaultConversionServiceTests.java index e07b2876..8687d31d 100644 --- a/spring-binding/src/test/java/org/springframework/binding/convert/support/DefaultConversionServiceTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/convert/support/DefaultConversionServiceTests.java @@ -54,7 +54,8 @@ public class DefaultConversionServiceTests extends TestCase { DefaultConversionService service = new DefaultConversionService(); - ConversionExecutor executor = service.getConversionExecutor(String.class, Boolean.class); + ConversionExecutorImpl executor = (ConversionExecutorImpl) service.getConversionExecutor(String.class, + Boolean.class); assertNotSame(customConverter, executor.getConverter()); try { executor.execute("ja"); @@ -65,7 +66,7 @@ public class DefaultConversionServiceTests extends TestCase { service.addConverter(customConverter); - executor = service.getConversionExecutor(String.class, Boolean.class); + executor = (ConversionExecutorImpl) service.getConversionExecutor(String.class, Boolean.class); assertSame(customConverter, executor.getConverter()); assertTrue(((Boolean) executor.execute("ja")).booleanValue()); }