diff --git a/jOOQ/src/main/java/org/jooq/Configuration.java b/jOOQ/src/main/java/org/jooq/Configuration.java index 105e9fa191..06421e8f45 100644 --- a/jOOQ/src/main/java/org/jooq/Configuration.java +++ b/jOOQ/src/main/java/org/jooq/Configuration.java @@ -299,6 +299,12 @@ public interface Configuration extends Serializable { */ VisitListenerProvider[] visitListenerProviders(); + /** + * Get the configured ConverterProvider from this + * configuration. + */ + ConverterProvider converterProvider(); + /** * Retrieve the configured schema mapping. * @@ -398,6 +404,18 @@ public interface Configuration extends Serializable { */ Configuration set(VisitListenerProvider... newVisitListenerProviders); + /** + * Change this configuration to hold a new converter provider. + *

+ * This method is not thread-safe and should not be used in globally + * available Configuration objects. + * + * @param newConverterProvider The new converter provider to be contained in + * the changed configuration. + * @return The changed configuration. + */ + Configuration set(ConverterProvider newConverterProvider); + /** * Change this configuration to hold a new dialect. *

@@ -494,6 +512,16 @@ public interface Configuration extends Serializable { */ Configuration derive(VisitListenerProvider... newVisitListenerProviders); + /** + * Create a derived configuration from this one, with new converter + * provider. + * + * @param newConverterProvider The new converter provider to + * be contained in the derived configuration. + * @return The derived configuration. + */ + Configuration derive(ConverterProvider newConverterProvider); + /** * Create a derived configuration from this one, with a new dialect. * diff --git a/jOOQ/src/main/java/org/jooq/ConverterProvider.java b/jOOQ/src/main/java/org/jooq/ConverterProvider.java new file mode 100644 index 0000000000..4fd82ae9c9 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/ConverterProvider.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * 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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq; + +/** + * A ConverterProvider providers {@link Converter} implementations + * for any combination of types <T> and <U>. + * + * @author Lukas Eder + */ +public interface ConverterProvider { + + /** + * Provide a converter that can convert between <T> and + * <U> types. + */ + Converter provide(Class tType, Class uType); +} diff --git a/jOOQ/src/main/java/org/jooq/Converters.java b/jOOQ/src/main/java/org/jooq/Converters.java index 6e33f77851..2f628c435e 100644 --- a/jOOQ/src/main/java/org/jooq/Converters.java +++ b/jOOQ/src/main/java/org/jooq/Converters.java @@ -95,6 +95,44 @@ public class Converters implements Converter { return new Converters(c1, c2, c3, c4); } + /** + * Inverse a converter. + */ + public static Converter inverse(final Converter converter) { + return new Converter() { + + /** + * Generated UID + */ + private static final long serialVersionUID = -4307758248063822630L; + + @Override + public T from(U u) { + return converter.to(u); + } + + @Override + public U to(T t) { + return converter.from(t); + } + + @Override + public Class fromType() { + return converter.toType(); + } + + @Override + public Class toType() { + return converter.fromType(); + } + + @Override + public String toString() { + return "InverseConverter [ " + fromType().getName() + " -> " + toType().getName() + " ]"; + } + }; + } + Converters(Converter... chain) { this.chain = chain == null ? new Converter[0] : chain; } @@ -128,4 +166,21 @@ public class Converters implements Converter { public final Class toType() { return chain[chain.length - 1].toType(); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String separator = " -> "; + + sb.append("Converters [ "); + sb.append(chain[0].fromType().getName()); + + for (Converter converter : chain) { + sb.append(separator); + sb.append(converter.toType().getName()); + } + + sb.append(" ]"); + return sb.toString(); + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractConverter.java b/jOOQ/src/main/java/org/jooq/impl/AbstractConverter.java new file mode 100644 index 0000000000..41af0825df --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractConverter.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * 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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.impl; + +import org.jooq.Converter; + +/** + * @author Lukas Eder + */ +public abstract class AbstractConverter implements Converter { + + /** + * Generated UID + */ + private static final long serialVersionUID = 3739249904977790727L; + + private final Class fromType; + private final Class toType; + + public AbstractConverter(Class fromType, Class toType) { + this.fromType = fromType; + this.toType = toType; + } + + @Override + public final Class fromType() { + return fromType; + } + + @Override + public final Class toType() { + return toType; + } + + @Override + public String toString() { + return "Converter [ " + fromType().getName() + " -> " + toType().getName() + " ]"; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java index e95d07ea0b..98e8d6eb35 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java @@ -57,6 +57,7 @@ import javax.xml.bind.JAXB; import org.jooq.Configuration; import org.jooq.ConnectionProvider; +import org.jooq.ConverterProvider; import org.jooq.DSLContext; import org.jooq.ExecuteListenerProvider; import org.jooq.RecordListenerProvider; @@ -95,6 +96,7 @@ public class DefaultConfiguration implements Configuration { private transient RecordListenerProvider[] recordListenerProviders; private transient ExecuteListenerProvider[] executeListenerProviders; private transient VisitListenerProvider[] visitListenerProviders; + private transient ConverterProvider converterProvider; // Derived objects private org.jooq.SchemaMapping mapping; @@ -131,6 +133,7 @@ public class DefaultConfiguration implements Configuration { null, null, null, + null, dialect, SettingsTools.defaultSettings(), null @@ -153,6 +156,7 @@ public class DefaultConfiguration implements Configuration { configuration.recordListenerProviders(), configuration.executeListenerProviders(), configuration.visitListenerProviders(), + configuration.converterProvider(), configuration.dialect(), configuration.settings(), configuration.data() @@ -183,6 +187,7 @@ public class DefaultConfiguration implements Configuration { null, executeListenerProviders, null, + null, dialect, settings, data @@ -214,6 +219,7 @@ public class DefaultConfiguration implements Configuration { null, executeListenerProviders, null, + null, dialect, settings, data @@ -247,6 +253,42 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, visitListenerProviders, + null, + dialect, + settings, + data + ); + } + + /** + * This constructor is maintained for backwards-compatibility reasons. + * Spring users tend to construct this DefaultConfiguration + * through reflection. + * + * @deprecated Use + * {@link #DefaultConfiguration(ConnectionProvider, TransactionProvider, RecordMapperProvider, RecordListenerProvider[], ExecuteListenerProvider[], VisitListenerProvider[], ConverterProvider, SQLDialect, Settings, Map)} + * instead. This constructor is maintained to provide jOOQ 3.2, 3.3 backwards-compatibility if called with reflection from Spring configurations. + */ + @Deprecated + DefaultConfiguration( + ConnectionProvider connectionProvider, + TransactionProvider transactionProvider, + RecordMapperProvider recordMapperProvider, + RecordListenerProvider[] recordListenerProviders, + ExecuteListenerProvider[] executeListenerProviders, + VisitListenerProvider[] visitListenerProviders, + SQLDialect dialect, + Settings settings, + Map data) + { + this( + connectionProvider, + transactionProvider, + recordMapperProvider, + recordListenerProviders, + executeListenerProviders, + visitListenerProviders, + null, dialect, settings, data @@ -268,6 +310,7 @@ public class DefaultConfiguration implements Configuration { RecordListenerProvider[] recordListenerProviders, ExecuteListenerProvider[] executeListenerProviders, VisitListenerProvider[] visitListenerProviders, + ConverterProvider converterProvider, SQLDialect dialect, Settings settings, Map data) @@ -278,6 +321,7 @@ public class DefaultConfiguration implements Configuration { set(recordListenerProviders); set(executeListenerProviders); set(visitListenerProviders); + set(converterProvider); set(dialect); set(settings); @@ -326,6 +370,7 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, visitListenerProviders, + converterProvider, dialect, settings, data @@ -344,6 +389,7 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, visitListenerProviders, + converterProvider, dialect, settings, data @@ -362,6 +408,7 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, visitListenerProviders, + converterProvider, dialect, settings, data @@ -380,6 +427,7 @@ public class DefaultConfiguration implements Configuration { newRecordListenerProviders, executeListenerProviders, visitListenerProviders, + converterProvider, dialect, settings, data @@ -398,6 +446,7 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, newExecuteListenerProviders, visitListenerProviders, + converterProvider, dialect, settings, data @@ -416,6 +465,26 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, newVisitListenerProviders, + converterProvider, + dialect, + settings, + data + ); + } + + /** + * {@inheritDoc} + */ + @Override + public final Configuration derive(ConverterProvider newConverterProvider) { + return new DefaultConfiguration( + connectionProvider, + transactionProvider, + recordMapperProvider, + recordListenerProviders, + executeListenerProviders, + visitListenerProviders, + newConverterProvider, dialect, settings, data @@ -434,6 +503,7 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, visitListenerProviders, + converterProvider, newDialect, settings, data @@ -452,6 +522,7 @@ public class DefaultConfiguration implements Configuration { recordListenerProviders, executeListenerProviders, visitListenerProviders, + converterProvider, dialect, newSettings, data @@ -550,6 +621,15 @@ public class DefaultConfiguration implements Configuration { return this; } + @Override + public final Configuration set(ConverterProvider newConverterProvider) { + this.converterProvider = newConverterProvider != null + ? newConverterProvider + : new DefaultConverterProvider(); + + return this; + } + /** * {@inheritDoc} */ @@ -709,6 +789,14 @@ public class DefaultConfiguration implements Configuration { return visitListenerProviders; } + /** + * {@inheritDoc} + */ + @Override + public final ConverterProvider converterProvider() { + return converterProvider; + } + /** * {@inheritDoc} */ @@ -801,6 +889,10 @@ public class DefaultConfiguration implements Configuration { oos.writeObject(cloneSerializables(executeListenerProviders)); oos.writeObject(cloneSerializables(recordListenerProviders)); oos.writeObject(cloneSerializables(visitListenerProviders)); + + oos.writeObject(converterProvider instanceof Serializable + ? converterProvider + : null); } private E[] cloneSerializables(E[] array) { @@ -824,5 +916,6 @@ public class DefaultConfiguration implements Configuration { executeListenerProviders = (ExecuteListenerProvider[]) ois.readObject(); recordListenerProviders = (RecordListenerProvider[]) ois.readObject(); visitListenerProviders = (VisitListenerProvider[]) ois.readObject(); + converterProvider = (ConverterProvider) ois.readObject(); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultConverterProvider.java b/jOOQ/src/main/java/org/jooq/impl/DefaultConverterProvider.java new file mode 100644 index 0000000000..d3387be101 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultConverterProvider.java @@ -0,0 +1,253 @@ +/** + * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com) + * All rights reserved. + * + * This work is dual-licensed + * - under the Apache Software License 2.0 (the "ASL") + * - under the jOOQ License and Maintenance Agreement (the "jOOQ License") + * ============================================================================= + * You may choose which license applies to you: + * + * - If you're using this work with Open Source databases, you may choose + * either ASL or jOOQ License. + * - If you're using this work with at least one commercial database, you must + * choose jOOQ License + * + * For more information, please visit http://www.jooq.org/licenses + * + * Apache Software License 2.0: + * ----------------------------------------------------------------------------- + * 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. + * + * jOOQ License and Maintenance Agreement: + * ----------------------------------------------------------------------------- + * Data Geekery grants the Customer the non-exclusive, timely limited and + * non-transferable license to install and use the Software under the terms of + * the jOOQ License and Maintenance Agreement. + * + * This library is distributed with a LIMITED WARRANTY. See the jOOQ License + * and Maintenance Agreement for more details: http://www.jooq.org/licensing + */ +package org.jooq.impl; + +import static org.jooq.Converters.inverse; +import static org.jooq.tools.StringUtils.rightPad; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.jooq.Converter; +import org.jooq.ConverterProvider; +import org.jooq.Converters; + +/** + * @author Lukas Eder + */ +public class DefaultConverterProvider implements ConverterProvider { + + final Graph graph = new Graph(); + + @SuppressWarnings("unchecked") + @Override + public Converter provide(Class tType, Class uType) { + if (tType == uType) + return (Converter) new IdentityConverter(tType); + else + return graph.get(new Endpoints(tType, uType)); + } + + public void add(Converter converter) { + graph.add(converter); + } + + @Override + public String toString() { + return graph.toString(); + } + + /** + * A graph modelling all the possible conversion paths. + */ + static class Graph { + + final Set> vertices; + final Map, Set>> adjacency; + final Map, Converter> paths; + + Graph() { + vertices = new HashSet>(); + adjacency = new HashMap, Set>>(); + paths = new ConcurrentHashMap, Converter>(); + } + + /** + * Get a converter for a pair of classes. + */ + @SuppressWarnings("unchecked") + Converter get(Endpoints classes) { + build(); + return (Converter) paths.get(classes); + } + + /** + * Add a Converter to the graph. + */ + void add(Converter converter) { + synchronized (paths) { + paths.clear(); + + Class t = converter.fromType(); + Class u = converter.toType(); + + vertices.add(t); + vertices.add(u); + + Set> tSet = adjacency.get(t); + if (tSet == null) { + tSet = new HashSet>(); + adjacency.put(t, tSet); + } + tSet.add(converter); + + Set> uSet = adjacency.get(u); + if (uSet == null) { + uSet = new HashSet>(); + adjacency.put(u, uSet); + } + uSet.add(inverse(converter)); + } + } + + /** + * Build the graph. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void build() { + if (paths.isEmpty()) { + synchronized (paths) { + for (Entry, Set>> entry : adjacency.entrySet()) { + for (Converter converter : entry.getValue()) { + path(new Endpoints(converter.fromType(), converter.toType()), converter); + } + } + + int size; + do { + size = paths.size(); + + List> keys = new ArrayList>(paths.keySet()); + for (Endpoints key : keys) { + for (Converter converter : adjacency.get(key.toType)) { + path(new Endpoints(key.fromType, converter.toType()), Converters.of((Converter) paths.get(key), converter)); + } + } + } + while (size < paths.size()); + } + } + } + + /** + * Add a path configuration if there isn't already one for the given + * endpoints. + */ + private void path(Endpoints key, Converter converter) { + if (key.fromType != key.toType) + if (!paths.containsKey(key)) + paths.put(key, converter); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public String toString() { + build(); + + synchronized (paths) { + StringBuilder sb = new StringBuilder(); + + Class[] classes = vertices.toArray(new Class[0]); + Arrays.sort(classes, new Comparator>() { + @Override + public int compare(Class o1, Class o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + int maxLength = Integer.MIN_VALUE; + for (Class c : classes) + maxLength = Math.max(maxLength, c.getName().length()); + + String sep1 = ""; + for (Class c1 : classes) { + sb.append(sep1); + sb.append(rightPad(c1.getName(), maxLength)); + + for (Class c2 : classes) { + if (paths.containsKey(new Endpoints(c1, c2))) { + sb.append("\n -> ") + .append(c2.getName()); + } + } + + sep1 = "\n\n"; + } + + return sb.toString(); + } + } + } + + /** + * A type modelling two end points inside of a graph. + */ + static class Endpoints { + final Class fromType; + final Class toType; + + Endpoints(Class t, Class u) { + this.fromType = t; + this.toType = u; + } + + @Override + public int hashCode() { + return 17 * fromType.hashCode() + toType.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + + if (obj instanceof Endpoints) { + Endpoints that = (Endpoints) obj; + return fromType == that.fromType && toType == that.toType; + } + + return false; + } + + @Override + public String toString() { + return "(" + fromType.getName() + ", " + toType.getName() + ")"; + } + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/EnumConverter.java b/jOOQ/src/main/java/org/jooq/impl/EnumConverter.java index 94981c8fe7..c9e501586f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/EnumConverter.java +++ b/jOOQ/src/main/java/org/jooq/impl/EnumConverter.java @@ -45,30 +45,25 @@ import static org.jooq.tools.Convert.convert; import java.util.LinkedHashMap; import java.util.Map; -import org.jooq.Converter; - /** * A base class for enum conversion. * * @author Lukas Eder */ -public class EnumConverter> implements Converter { +public class EnumConverter> extends AbstractConverter { /** * Generated UID */ private static final long serialVersionUID = -6094337837408829491L; - private final Class fromType; - private final Class toType; private final Map lookup; private final EnumType enumType; public EnumConverter(Class fromType, Class toType) { - this.fromType = fromType; - this.toType = toType; - this.enumType = Number.class.isAssignableFrom(fromType) ? EnumType.ORDINAL : EnumType.STRING; + super(fromType, toType); + this.enumType = Number.class.isAssignableFrom(fromType) ? EnumType.ORDINAL : EnumType.STRING; this.lookup = new LinkedHashMap(); for (U u : toType.getEnumConstants()) { this.lookup.put(to(u), u); @@ -92,23 +87,13 @@ public class EnumConverter> implements Converter { return null; } else if (enumType == EnumType.ORDINAL) { - return convert(userObject.ordinal(), fromType); + return convert(userObject.ordinal(), fromType()); } else { - return convert(userObject.name(), fromType); + return convert(userObject.name(), fromType()); } } - @Override - public final Class fromType() { - return fromType; - } - - @Override - public final Class toType() { - return toType; - } - /** * The type of the converted Enum. *

@@ -129,6 +114,6 @@ public class EnumConverter> implements Converter { @Override public String toString() { - return "EnumConverter [ from : " + fromType.getName() + ", to : " + toType.getName() + " ]"; + return "EnumConverter [ " + fromType().getName() + " -> " + toType().getName() + " ]"; } } diff --git a/jOOQ/src/test/java/org/jooq/test/ConverterTest.java b/jOOQ/src/test/java/org/jooq/test/ConverterTest.java index ba832e9bc8..9a7aabe3f3 100644 --- a/jOOQ/src/test/java/org/jooq/test/ConverterTest.java +++ b/jOOQ/src/test/java/org/jooq/test/ConverterTest.java @@ -41,13 +41,16 @@ package org.jooq.test; import static org.jooq.test.data.BoolTable.BOOL_TABLE; +import static org.junit.Assert.assertNull; import java.sql.SQLException; import org.jooq.Converter; import org.jooq.Converters; import org.jooq.Result; +import org.jooq.impl.AbstractConverter; import org.jooq.impl.DSL; +import org.jooq.impl.DefaultConverterProvider; import org.jooq.test.data.BoolRecord; import org.jooq.test.data.converter.Bool; import org.jooq.tools.jdbc.MockConnection; @@ -150,4 +153,185 @@ public class ConverterTest extends AbstractTest { assertEquals(1, (int) c4.from(c4.to(1))); assertEquals(1, (int) c5.from(c5.to(1))); } + + @SuppressWarnings("serial") + @Test + public void testConverterGraph() { + Converter ab = new AbstractConverter(A.class, B.class) { + @Override + public B from(A v) { + return new B(v.v); + } + + @Override + public A to(B v) { + return new A(v.v); + } + }; + + + Converter ac = new AbstractConverter(A.class, C.class) { + @Override + public C from(A v) { + return new C(v.v); + } + + @Override + public A to(C v) { + return new A(v.v); + } + }; + + Converter cd = new AbstractConverter(C.class, D.class) { + @Override + public D from(C v) { + return new D(v.v); + } + + @Override + public C to(D v) { + return new C(v.v); + } + }; + + Converter ce = new AbstractConverter(C.class, E.class) { + @Override + public E from(C v) { + return new E(v.v); + } + + @Override + public C to(E v) { + return new C(v.v); + } + }; + + DefaultConverterProvider provider = new DefaultConverterProvider(); + provider.add(ab); + provider.add(ac); + provider.add(cd); + provider.add(ce); + + // F is not part of the graph + assertNull(provider.provide(A.class, F.class)); + assertNull(provider.provide(B.class, F.class)); + assertNull(provider.provide(C.class, F.class)); + assertNull(provider.provide(D.class, F.class)); + assertNull(provider.provide(E.class, F.class)); + assertNull(provider.provide(F.class, A.class)); + assertNull(provider.provide(F.class, A.class)); + assertNull(provider.provide(F.class, A.class)); + assertNull(provider.provide(F.class, A.class)); + assertNull(provider.provide(F.class, A.class)); + + // Identity conversion + assertEquals("a", provider.provide(A.class, A.class).from(new A("")).v); + assertEquals("a", provider.provide(A.class, A.class).to(new A("")).v); + assertEquals("b", provider.provide(B.class, B.class).from(new B("")).v); + assertEquals("b", provider.provide(B.class, B.class).to(new B("")).v); + assertEquals("c", provider.provide(C.class, C.class).from(new C("")).v); + assertEquals("c", provider.provide(C.class, C.class).to(new C("")).v); + assertEquals("d", provider.provide(D.class, D.class).from(new D("")).v); + assertEquals("d", provider.provide(D.class, D.class).to(new D("")).v); + assertEquals("e", provider.provide(E.class, E.class).from(new E("")).v); + assertEquals("e", provider.provide(E.class, E.class).to(new E("")).v); + assertEquals("f", provider.provide(F.class, F.class).from(new F("")).v); + assertEquals("f", provider.provide(F.class, F.class).to(new F("")).v); + + // Rest of the graph + assertEquals("ab", provider.provide(A.class, B.class).from(new A("")).v); + assertEquals("ba", provider.provide(A.class, B.class).to(new B("")).v); + assertEquals("ac", provider.provide(A.class, C.class).from(new A("")).v); + assertEquals("ca", provider.provide(A.class, C.class).to(new C("")).v); + assertEquals("acd", provider.provide(A.class, D.class).from(new A("")).v); + assertEquals("dca", provider.provide(A.class, D.class).to(new D("")).v); + assertEquals("ace", provider.provide(A.class, E.class).from(new A("")).v); + assertEquals("eca", provider.provide(A.class, E.class).to(new E("")).v); + + assertEquals("ba", provider.provide(B.class, A.class).from(new B("")).v); + assertEquals("ab", provider.provide(B.class, A.class).to(new A("")).v); + assertEquals("bac", provider.provide(B.class, C.class).from(new B("")).v); + assertEquals("cab", provider.provide(B.class, C.class).to(new C("")).v); + assertEquals("bacd", provider.provide(B.class, D.class).from(new B("")).v); + assertEquals("dcab", provider.provide(B.class, D.class).to(new D("")).v); + assertEquals("bace", provider.provide(B.class, E.class).from(new B("")).v); + assertEquals("ecab", provider.provide(B.class, E.class).to(new E("")).v); + + assertEquals("ca", provider.provide(C.class, A.class).from(new C("")).v); + assertEquals("ac", provider.provide(C.class, A.class).to(new A("")).v); + assertEquals("cab", provider.provide(C.class, B.class).from(new C("")).v); + assertEquals("bac", provider.provide(C.class, B.class).to(new B("")).v); + assertEquals("cd", provider.provide(C.class, D.class).from(new C("")).v); + assertEquals("dc", provider.provide(C.class, D.class).to(new D("")).v); + assertEquals("ce", provider.provide(C.class, E.class).from(new C("")).v); + assertEquals("ec", provider.provide(C.class, E.class).to(new E("")).v); + + assertEquals("dca", provider.provide(D.class, A.class).from(new D("")).v); + assertEquals("acd", provider.provide(D.class, A.class).to(new A("")).v); + assertEquals("dcab", provider.provide(D.class, B.class).from(new D("")).v); + assertEquals("bacd", provider.provide(D.class, B.class).to(new B("")).v); + assertEquals("dc", provider.provide(D.class, C.class).from(new D("")).v); + assertEquals("cd", provider.provide(D.class, C.class).to(new C("")).v); + assertEquals("dce", provider.provide(D.class, E.class).from(new D("")).v); + assertEquals("ecd", provider.provide(D.class, E.class).to(new E("")).v); + + assertEquals("eca", provider.provide(E.class, A.class).from(new E("")).v); + assertEquals("ace", provider.provide(E.class, A.class).to(new A("")).v); + assertEquals("ecab", provider.provide(E.class, B.class).from(new E("")).v); + assertEquals("bace", provider.provide(E.class, B.class).to(new B("")).v); + assertEquals("ec", provider.provide(E.class, C.class).from(new E("")).v); + assertEquals("ce", provider.provide(E.class, C.class).to(new C("")).v); + assertEquals("ecd", provider.provide(E.class, D.class).from(new E("")).v); + assertEquals("dce", provider.provide(E.class, D.class).to(new D("")).v); + + System.out.println(provider); + } + + static class A { + final String v; + + A(String v) { + this.v = v + "a"; + } + } + + static class B { + final String v; + + B(String v) { + this.v = v + "b"; + } + } + + static class C { + final String v; + + C(String v) { + this.v = v + "c"; + } + } + + static class D { + final String v; + + D(String v) { + this.v = v + "d"; + } + } + + static class E { + final String v; + + E(String v) { + this.v = v + "e"; + } + } + + static class F { + final String v; + + F(String v) { + this.v = v + "f"; + } + } }