From dc1448749a653e651b9d1a682560658ce578ea80 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 20 Apr 2017 08:41:02 +0200 Subject: [PATCH 1/5] DATACMNS-1034 - Prepare feature branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1db9b70ebb..c28c7cb4e8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATACMNS-1034-SNAPSHOT Spring Data Core From e9438ed36e8e41ddccf1ae3534ac5d9682a2c115 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 20 Apr 2017 10:09:10 +0200 Subject: [PATCH 2/5] DATACMNS-1034 - Introduced API to easily register converters using Java 8 lambdas. Introduced ConverterBuilder which exposes API to register Spring GenericConverters for the use with a store module's CustomConversions. This allows simple registration of such converters using Java 8 lambdas. ConverterAware converters = ConverterBuilder.reading(String.class, Long.class, it -> Long.valueOf(it)).andWriting(it -> Object::toString); The setup can also be done from the reading side which would just need the method invocations inverted. The resulting ConverterAware will expose the registered converters so that they can be easily handed to a CustomConversions instance. Partial creation of either reading or writing converters is possible, too with the returned instance then only exposing one of the two converters. --- .../data/convert/ConverterBuilder.java | 157 +++++++++++++++++ .../data/convert/DefaultConverterBuilder.java | 159 ++++++++++++++++++ .../convert/ConverterBuilderUnitTests.java | 89 ++++++++++ 3 files changed, 405 insertions(+) create mode 100644 src/main/java/org/springframework/data/convert/ConverterBuilder.java create mode 100644 src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java create mode 100644 src/test/java/org/springframework/data/convert/ConverterBuilderUnitTests.java diff --git a/src/main/java/org/springframework/data/convert/ConverterBuilder.java b/src/main/java/org/springframework/data/convert/ConverterBuilder.java new file mode 100644 index 0000000000..6347869f5c --- /dev/null +++ b/src/main/java/org/springframework/data/convert/ConverterBuilder.java @@ -0,0 +1,157 @@ +/* + * Copyright 2017 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.data.convert; + +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; +import org.springframework.util.Assert; + +/** + * API to easily set up {@link GenericConverter} instances using Java 8 lambdas, mostly in bidirectional fashion for + * easy registration as custom type converters of the SPring Data mapping subsystem. The registration starts either with + * the definition of a reading or writing converter that can then be completed + * + * @author Oliver Gierke + * @since 2.0 + * @see #reading(Class, Class, Function) + * @see #writing(Class, Class, Function) + * @soundtrack John Mayer - Moving On and Getting Over (The Search for Everything) + */ +public interface ConverterBuilder { + + /** + * Creates a new {@link ReadingConverterBuilder} to produce a converter to read values of the given source (the store + * type) into the given target (the domain type). + * + * @param source must not be {@literal null}. + * @param target must not be {@literal null}. + * @param function must not be {@literal null}. + * @return + */ + static ReadingConverterBuilder reading(Class source, Class target, + Function function) { + + Assert.notNull(source, "Source type must not be null!"); + Assert.notNull(target, "Target type must not be null!"); + Assert.notNull(function, "Conversion function must not be null!"); + + return new DefaultConverterBuilder<>(new ConvertiblePair(source, target), Optional.empty(), Optional.of(function)); + } + + /** + * Creates a new {@link WritingConverterBuilder} to produce a converter to write values of the given source (the + * domain type type) into the given target (the store type). + * + * @param source must not be {@literal null}. + * @param target must not be {@literal null}. + * @param function must not be {@literal null}. + * @return + */ + static WritingConverterBuilder writing(Class source, Class target, + Function function) { + + Assert.notNull(source, "Source type must not be null!"); + Assert.notNull(target, "Target type must not be null!"); + Assert.notNull(function, "Conversion function must not be null!"); + + return new DefaultConverterBuilder<>(new ConvertiblePair(target, source), Optional.of(function), Optional.empty()); + } + + /** + * Returns all {@link GenericConverter} instances to be registered for the current {@link ConverterBuilder}. + * + * @return + */ + Set getConverters(); + + /** + * Exposes a writing converter. + * + * @author Oliver Gierke + * @since 2.0 + */ + interface WritingConverterAware { + + /** + * Returns the writing converter already created. + * + * @return + */ + GenericConverter getWritingConverter(); + } + + /** + * Exposes a reading converter. + * + * @author Oliver Gierke + * @since 2.0 + */ + interface ReadingConverterAware { + + /** + * Returns the reading converter already created. + * + * @return + */ + GenericConverter getReadingConverter(); + } + + /** + * Interface to represent an intermediate setup step of {@link ConverterAware} defining a reading converter first. + * + * @author Oliver Gierke + * @since 2.0 + */ + interface ReadingConverterBuilder extends ConverterBuilder, ReadingConverterAware { + + /** + * Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. + * + * @param function must not be {@literal null}. + * @return + */ + ConverterAware andWriting(Function function); + } + + /** + * Interface to represent an intermediate setup step of {@link ConverterAware} defining a writing converter first. + * + * @author Oliver Gierke + * @since 2.0 + */ + interface WritingConverterBuilder extends ConverterBuilder, WritingConverterAware { + + /** + * Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. + * + * @param function must not be {@literal null}. + * @return + */ + ConverterAware andReading(Function function); + } + + /** + * A {@link ConverterBuilder} ware of both a reading and writing converter. + * + * @author Oliver Gierke + * @since 2.0 + */ + interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {} +} diff --git a/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java b/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java new file mode 100644 index 0000000000..bfcc47fb1f --- /dev/null +++ b/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java @@ -0,0 +1,159 @@ +/* + * Copyright 2017 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.data.convert; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Wither; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; +import org.springframework.data.convert.ConverterBuilder.ConverterAware; +import org.springframework.data.convert.ConverterBuilder.ReadingConverterBuilder; +import org.springframework.data.convert.ConverterBuilder.WritingConverterBuilder; +import org.springframework.data.util.Optionals; + +/** + * Builder to easily set up (bi-directional) {@link Converter} instances for Spring Data type mapping using Lambdas. Use + * factory methods on {@link ConverterBuilder} to create instances of this class. + * + * @author Oliver Gierke + * @since 2.0 + * @see ConverterBuilder#writing(Class, Class, Function) + * @see ConverterBuilder#reading(Class, Class, Function) + * @soundtrack John Mayer - Still Feel Like Your Man (The Search for Everything) + */ +@Wither(AccessLevel.PACKAGE) +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) +class DefaultConverterBuilder + implements ConverterAware, ReadingConverterBuilder, WritingConverterBuilder { + + private final @NonNull ConvertiblePair convertiblePair; + private final @NonNull Optional> writing; + private final @NonNull Optional> reading; + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.WritingConverterBuilder#andReading(java.util.function.Function) + */ + @Override + public ConverterAware andReading(Function function) { + return withReading(Optional.of(function)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.ReadingConverterBuilder#andWriting(java.util.function.Function) + */ + @Override + public ConverterAware andWriting(Function function) { + return withWriting(Optional.of(function)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.ReadingConverterBuilder#getRequiredReadingConverter() + */ + @Override + public GenericConverter getReadingConverter() { + return getOptionalReadingConverter() + .orElseThrow(() -> new IllegalStateException("No reading converter specified!")); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.WritingConverterBuilder#getRequiredWritingConverter() + */ + @Override + public GenericConverter getWritingConverter() { + return getOptionalWritingConverter() + .orElseThrow(() -> new IllegalStateException("No writing converter specified!")); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.convert.ConverterBuilder#getConverters() + */ + @Override + public Set getConverters() { + return Optionals.toStream(getOptionalReadingConverter(), getOptionalWritingConverter()).collect(Collectors.toSet()); + } + + private Optional getOptionalReadingConverter() { + return reading.map(it -> new ConfigurableGenericConverter.Reading<>(convertiblePair, it)); + } + + private Optional getOptionalWritingConverter() { + return writing.map(it -> new ConfigurableGenericConverter.Writing<>(invertedPair(), it)); + } + + private ConvertiblePair invertedPair() { + return new ConvertiblePair(convertiblePair.getTargetType(), convertiblePair.getSourceType()); + } + + @RequiredArgsConstructor + @EqualsAndHashCode + private static class ConfigurableGenericConverter implements GenericConverter { + + private final ConvertiblePair convertiblePair; + private final Function function; + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ + @Override + @SuppressWarnings("unchecked") + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + return function.apply((S) source); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() + */ + @Override + public Set getConvertibleTypes() { + return Collections.singleton(convertiblePair); + } + + @WritingConverter + private static class Writing extends ConfigurableGenericConverter { + + public Writing(ConvertiblePair convertiblePair, Function function) { + super(convertiblePair, function); + } + } + + @ReadingConverter + private static class Reading extends ConfigurableGenericConverter { + + public Reading(ConvertiblePair convertiblePair, Function function) { + super(convertiblePair, function); + } + } + } +} diff --git a/src/test/java/org/springframework/data/convert/ConverterBuilderUnitTests.java b/src/test/java/org/springframework/data/convert/ConverterBuilderUnitTests.java new file mode 100644 index 0000000000..7213e9408f --- /dev/null +++ b/src/test/java/org/springframework/data/convert/ConverterBuilderUnitTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017 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.data.convert; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; +import org.mockito.internal.util.Supplier; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; +import org.springframework.data.convert.ConverterBuilder.ConverterAware; +import org.springframework.data.convert.ConverterBuilder.ReadingConverterBuilder; +import org.springframework.data.convert.ConverterBuilder.WritingConverterBuilder; + +/** + * Unit tests for {@link DefaultConverterBuilder}. + * + * @author Oliver Gierke + * @since 2.0 + * @soundtrack John Mayer - In the Blood (The Search for Everything) + */ +public class ConverterBuilderUnitTests { + + @Test // DATACMNS-1034 + public void setsUpBidirectionalConvertersFromReading() { + + ConverterAware builder = ConverterBuilder.reading(String.class, Long.class, it -> Long.valueOf(it)) + .andWriting(Object::toString); + + assertConverter(builder.getReadingConverter(), "1", 1L); + assertConverter(builder.getWritingConverter(), 1L, "1"); + } + + @Test // DATACMNS-1034 + public void setsUpBidirectionalConvertersFromWriting() { + + ConverterAware builder = ConverterBuilder.writing(Long.class, String.class, Object::toString) + .andReading(it -> Long.valueOf(it)); + + assertConverter(builder.getReadingConverter(), "1", 1L); + assertConverter(builder.getWritingConverter(), 1L, "1"); + } + + @Test // DATACMNS-1034 + public void setsUpReadingConverter() { + + ReadingConverterBuilder builder = ConverterBuilder.reading(String.class, Long.class, + string -> Long.valueOf(string)); + + assertConverter(builder.getReadingConverter(), "1", 1L); + assertOnlyConverter(builder, builder::getReadingConverter); + } + + @Test // DATACMNS-1034 + public void setsUpWritingConverter() { + + WritingConverterBuilder builder = ConverterBuilder.writing(Long.class, String.class, + Object::toString); + + assertConverter(builder.getWritingConverter(), 1L, "1"); + assertOnlyConverter(builder, builder::getWritingConverter); + } + + private static void assertConverter(GenericConverter converter, Object source, Object target) { + + assertThat(converter.getConvertibleTypes()) + .containsExactly(new ConvertiblePair(source.getClass(), target.getClass())); + assertThat(converter.convert(source, TypeDescriptor.forObject(source), TypeDescriptor.forObject(target))) + .isEqualTo(target); + } + + private static void assertOnlyConverter(ConverterBuilder builder, Supplier supplier) { + assertThat(builder.getConverters()).containsExactly(supplier.get()); + } +} From 3436c63ab286cc7941653596c3405f3658db7433 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 21 Apr 2017 17:54:34 +0200 Subject: [PATCH 3/5] DAATCMNS-1034 - Improvements to new ConverterBuilder API. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made nested API interfaces public so that they can actually be used. CustomConversions now considers ConverterAware and treats them appropriately for ConvertiblePair registration as well as during the registration in the ConversionService. Tweaked parameter types in CustomConversions to rather accept a Collection for the converters (previously List). Also, registerConverterIn(…) now takes a ConverterRegistry over a GenericConversionService. Polished Javadoc and non-null assertions. --- .../data/convert/ConverterBuilder.java | 10 +-- .../data/convert/CustomConversions.java | 72 ++++++++++++------- .../convert/CustomConversionsUnitTests.java | 24 +++++++ 3 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/ConverterBuilder.java b/src/main/java/org/springframework/data/convert/ConverterBuilder.java index 6347869f5c..5cdc388429 100644 --- a/src/main/java/org/springframework/data/convert/ConverterBuilder.java +++ b/src/main/java/org/springframework/data/convert/ConverterBuilder.java @@ -87,7 +87,7 @@ static WritingConverterBuilder writing(Class source, Class ta * @author Oliver Gierke * @since 2.0 */ - interface WritingConverterAware { + public interface WritingConverterAware { /** * Returns the writing converter already created. @@ -103,7 +103,7 @@ interface WritingConverterAware { * @author Oliver Gierke * @since 2.0 */ - interface ReadingConverterAware { + public interface ReadingConverterAware { /** * Returns the reading converter already created. @@ -119,7 +119,7 @@ interface ReadingConverterAware { * @author Oliver Gierke * @since 2.0 */ - interface ReadingConverterBuilder extends ConverterBuilder, ReadingConverterAware { + public interface ReadingConverterBuilder extends ConverterBuilder, ReadingConverterAware { /** * Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. @@ -136,7 +136,7 @@ interface ReadingConverterBuilder extends ConverterBuilder, ReadingConvert * @author Oliver Gierke * @since 2.0 */ - interface WritingConverterBuilder extends ConverterBuilder, WritingConverterAware { + public interface WritingConverterBuilder extends ConverterBuilder, WritingConverterAware { /** * Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. @@ -153,5 +153,5 @@ interface WritingConverterBuilder extends ConverterBuilder, WritingConvert * @author Oliver Gierke * @since 2.0 */ - interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {} + public interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {} } diff --git a/src/main/java/org/springframework/data/convert/CustomConversions.java b/src/main/java/org/springframework/data/convert/CustomConversions.java index d8d1cc7507..c17e1d4dea 100644 --- a/src/main/java/org/springframework/data/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/convert/CustomConversions.java @@ -37,9 +37,11 @@ import org.springframework.core.GenericTypeResolver; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.ConverterBuilder.ConverterAware; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.Optionals; import org.springframework.data.util.Streamable; @@ -90,10 +92,12 @@ public class CustomConversions { /** * Creates a new {@link CustomConversions} instance registering the given converters. * - * @param converters + * @param storeConversions must not be {@literal null}. + * @param converters must not be {@literal null}. */ - public CustomConversions(StoreConversions storeConversions, List converters) { + public CustomConversions(StoreConversions storeConversions, Collection converters) { + Assert.notNull(storeConversions, "StoreConversions must not be null!"); Assert.notNull(converters, "List of converters must not be null!"); this.readingPairs = new LinkedHashSet<>(); @@ -149,33 +153,46 @@ public boolean isSimpleType(Class type) { * * @param conversionService */ - public void registerConvertersIn(GenericConversionService conversionService) { + public void registerConvertersIn(ConverterRegistry conversionService) { Assert.notNull(conversionService, "ConversionService must not be null!"); - converters.forEach(it -> { + converters.forEach(it -> registerConverterIn(it, conversionService)); + } - boolean added = false; + /** + * Registers the given converter in the given {@link GenericConversionService}. + * + * @param candidate must not be {@literal null}. + * @param conversionService must not be {@literal null}. + */ + private void registerConverterIn(Object candidate, ConverterRegistry conversionService) { - if (it instanceof Converter) { - conversionService.addConverter(Converter.class.cast(it)); - added = true; - } + boolean added = false; - if (it instanceof ConverterFactory) { - conversionService.addConverterFactory(ConverterFactory.class.cast(it)); - added = true; - } + if (candidate instanceof Converter) { + conversionService.addConverter(Converter.class.cast(candidate)); + added = true; + } - if (it instanceof GenericConverter) { - conversionService.addConverter(GenericConverter.class.cast(it)); - added = true; - } + if (candidate instanceof ConverterFactory) { + conversionService.addConverterFactory(ConverterFactory.class.cast(candidate)); + added = true; + } - if (!added) { - throw new IllegalArgumentException(String.format(NOT_A_CONVERTER, it)); - } - }); + if (candidate instanceof GenericConverter) { + conversionService.addConverter(GenericConverter.class.cast(candidate)); + added = true; + } + + if (candidate instanceof ConverterAware) { + ConverterAware.class.cast(candidate).getConverters().forEach(it -> registerConverterIn(it, conversionService)); + added = true; + } + + if (!added) { + throw new IllegalArgumentException(String.format(NOT_A_CONVERTER, candidate)); + } } /** @@ -460,10 +477,15 @@ public Streamable getRegistrationsFor(Object converter) { boolean isWriting = type.isAnnotationPresent(WritingConverter.class); boolean isReading = type.isAnnotationPresent(ReadingConverter.class); - if (converter instanceof GenericConverter) { + if (converter instanceof ConverterAware) { + + return Streamable.of(() -> ConverterAware.class.cast(converter).getConverters().stream()// + .flatMap(it -> getRegistrationsFor(it).stream())); + + } else if (converter instanceof GenericConverter) { - GenericConverter genericConverter = (GenericConverter) converter; - return Streamable.of(genericConverter.getConvertibleTypes()).map(it -> register(it, isReading, isWriting)); + return Streamable.of(GenericConverter.class.cast(converter).getConvertibleTypes())// + .map(it -> register(it, isReading, isWriting)); } else if (converter instanceof ConverterFactory) { @@ -474,7 +496,7 @@ public Streamable getRegistrationsFor(Object converter) { return getRegistrationFor(converter, Converter.class, isReading, isWriting); } else { - throw new IllegalArgumentException("Unsupported converter type!"); + throw new IllegalArgumentException(String.format("Unsupported converter type %s!", converter)); } } diff --git a/src/test/java/org/springframework/data/convert/CustomConversionsUnitTests.java b/src/test/java/org/springframework/data/convert/CustomConversionsUnitTests.java index 371824cb06..8b349f4926 100644 --- a/src/test/java/org/springframework/data/convert/CustomConversionsUnitTests.java +++ b/src/test/java/org/springframework/data/convert/CustomConversionsUnitTests.java @@ -32,8 +32,10 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.ConverterBuilder.ConverterAware; import org.springframework.data.convert.CustomConversions.StoreConversions; import org.threeten.bp.LocalDateTime; @@ -158,6 +160,24 @@ public void registersConverterFactoryCorrectly() { assertThat(customConversions.getCustomWriteTarget(String.class, SimpleDateFormat.class)).isPresent(); } + @Test // DATACMNS-1034 + public void registersConverterFromConverterAware() { + + ConverterAware converters = ConverterBuilder.reading(Left.class, Right.class, left -> new Right()) + .andWriting(right -> new Left()); + + CustomConversions conversions = new CustomConversions(StoreConversions.NONE, Collections.singletonList(converters)); + + assertThat(conversions.hasCustomWriteTarget(Right.class)).isTrue(); + assertThat(conversions.hasCustomReadTarget(Left.class, Right.class)).isTrue(); + + ConfigurableConversionService conversionService = new GenericConversionService(); + conversions.registerConvertersIn(conversionService); + + assertThat(conversionService.canConvert(Left.class, Right.class)).isTrue(); + assertThat(conversionService.canConvert(Right.class, Left.class)).isTrue(); + } + private static Class createProxyTypeFor(Class type) { ProxyFactory factory = new ProxyFactory(); @@ -274,4 +294,8 @@ public T convert(String source) { } } } + + static class Left {} + + static class Right {} } From 20d2f433216be72c829600843f45170a4a88cdc2 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 21 Apr 2017 17:58:07 +0200 Subject: [PATCH 4/5] DATACMNS-1034 - Incorporate feedback from code review. --- .../springframework/data/convert/ConverterBuilder.java | 6 +++--- .../data/convert/DefaultConverterBuilder.java | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/ConverterBuilder.java b/src/main/java/org/springframework/data/convert/ConverterBuilder.java index 5cdc388429..615ab9b284 100644 --- a/src/main/java/org/springframework/data/convert/ConverterBuilder.java +++ b/src/main/java/org/springframework/data/convert/ConverterBuilder.java @@ -45,8 +45,8 @@ public interface ConverterBuilder { * @param function must not be {@literal null}. * @return */ - static ReadingConverterBuilder reading(Class source, Class target, - Function function) { + static ReadingConverterBuilder reading(Class source, Class target, + Function function) { Assert.notNull(source, "Source type must not be null!"); Assert.notNull(target, "Target type must not be null!"); @@ -148,7 +148,7 @@ public interface WritingConverterBuilder extends ConverterBuilder, Writing } /** - * A {@link ConverterBuilder} ware of both a reading and writing converter. + * A {@link ConverterBuilder} aware of both a reading and writing converter. * * @author Oliver Gierke * @since 2.0 diff --git a/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java b/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java index bfcc47fb1f..ddbfbd3ce7 100644 --- a/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java +++ b/src/main/java/org/springframework/data/convert/DefaultConverterBuilder.java @@ -99,7 +99,10 @@ public GenericConverter getWritingConverter() { */ @Override public Set getConverters() { - return Optionals.toStream(getOptionalReadingConverter(), getOptionalWritingConverter()).collect(Collectors.toSet()); + + return Optionals// + .toStream(getOptionalReadingConverter(), getOptionalWritingConverter())// + .collect(Collectors.toSet()); } private Optional getOptionalReadingConverter() { @@ -143,7 +146,7 @@ public Set getConvertibleTypes() { @WritingConverter private static class Writing extends ConfigurableGenericConverter { - public Writing(ConvertiblePair convertiblePair, Function function) { + Writing(ConvertiblePair convertiblePair, Function function) { super(convertiblePair, function); } } @@ -151,7 +154,7 @@ public Writing(ConvertiblePair convertiblePair, Function @ReadingConverter private static class Reading extends ConfigurableGenericConverter { - public Reading(ConvertiblePair convertiblePair, Function function) { + Reading(ConvertiblePair convertiblePair, Function function) { super(convertiblePair, function); } } From 04a148a3b1c79d1d21766c2339dc55646ee52277 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 25 Apr 2017 08:41:50 +0200 Subject: [PATCH 5/5] DATACMNS-1034 - Incorporate feedback from code review. Typos in ConverterBuilder. Removed obsolete visibility modifiers for nested types in ConverterBuilder. --- .../data/convert/ConverterBuilder.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/ConverterBuilder.java b/src/main/java/org/springframework/data/convert/ConverterBuilder.java index 615ab9b284..ee79e8a313 100644 --- a/src/main/java/org/springframework/data/convert/ConverterBuilder.java +++ b/src/main/java/org/springframework/data/convert/ConverterBuilder.java @@ -25,8 +25,8 @@ /** * API to easily set up {@link GenericConverter} instances using Java 8 lambdas, mostly in bidirectional fashion for - * easy registration as custom type converters of the SPring Data mapping subsystem. The registration starts either with - * the definition of a reading or writing converter that can then be completed + * easy registration as custom type converters of the Spring Data mapping subsystem. The registration starts either with + * the definition of a reading or writing converter that can then be completed. * * @author Oliver Gierke * @since 2.0 @@ -57,7 +57,7 @@ static ReadingConverterBuilder reading(Class source, Class ta /** * Creates a new {@link WritingConverterBuilder} to produce a converter to write values of the given source (the - * domain type type) into the given target (the store type). + * domain type) into the given target (the store type). * * @param source must not be {@literal null}. * @param target must not be {@literal null}. @@ -87,7 +87,7 @@ static WritingConverterBuilder writing(Class source, Class ta * @author Oliver Gierke * @since 2.0 */ - public interface WritingConverterAware { + interface WritingConverterAware { /** * Returns the writing converter already created. @@ -103,7 +103,7 @@ public interface WritingConverterAware { * @author Oliver Gierke * @since 2.0 */ - public interface ReadingConverterAware { + interface ReadingConverterAware { /** * Returns the reading converter already created. @@ -119,7 +119,7 @@ public interface ReadingConverterAware { * @author Oliver Gierke * @since 2.0 */ - public interface ReadingConverterBuilder extends ConverterBuilder, ReadingConverterAware { + interface ReadingConverterBuilder extends ConverterBuilder, ReadingConverterAware { /** * Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. @@ -136,7 +136,7 @@ public interface ReadingConverterBuilder extends ConverterBuilder, Reading * @author Oliver Gierke * @since 2.0 */ - public interface WritingConverterBuilder extends ConverterBuilder, WritingConverterAware { + interface WritingConverterBuilder extends ConverterBuilder, WritingConverterAware { /** * Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. @@ -153,5 +153,5 @@ public interface WritingConverterBuilder extends ConverterBuilder, Writing * @author Oliver Gierke * @since 2.0 */ - public interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {} + interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {} }