package joist.converter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import joist.util.Copy; import joist.util.TestCounter; public class ConverterRegistry { protected static final TestCounter probes = new TestCounter(); private final ConcurrentMap<ConverterKey, ConverterStub> converters = new ConcurrentHashMap<ConverterKey, ConverterStub>(); public static ConverterRegistry newRegistryWithDefaultConverters() { ConverterRegistry r = new ConverterRegistry(); for (Converter<?, ?> c : DefaultConverters.all) { r.addConverter(c); } return r; } public synchronized <T, U> void addConverter(Converter<T, U> c) { ConverterKey key1 = new ConverterKey(c.getTypeOne(), c.getTypeTwo()); ConverterStubOne<T, U> stub1 = new ConverterStubOne<T, U>(c); this.converters.put(key1, stub1); // This is kind of ugly--Converters were built to be inherently two-way--but they're not // always, so look for AbstractOneWayConverter as a marker interface. Maybe eventually go // back to one-way-only converters? Dunno. if (!(c instanceof AbstractOneWayConverter<?, ?>)) { ConverterKey key2 = new ConverterKey(c.getTypeTwo(), c.getTypeOne()); ConverterStubTwo<T, U> stub2 = new ConverterStubTwo<T, U>(c); this.converters.put(key2, stub2); } } public <T> T convert(Object value, Class<T> toType) { Class<?> valueClass = (value == null) ? Void.class : value.getClass(); ConverterKey key = new ConverterKey(valueClass, toType); ConverterStub stub = this.converters.get(key); if (stub != null) { return (T) stub.convert(value, toType); } // Do we even need to convert? ConverterRegistry.probes.next(); if (toType.isAssignableFrom(valueClass)) { this.converters.put(key, new NoopConverterStub()); return (T) value; } // We didn't find a converter with the default types, start walking up class/interface hierarchies for (Class<?> to : this.getSuperclassesAndInterfaces(toType)) { for (Class<?> from : this.getSuperclassesAndInterfaces(valueClass)) { ConverterKey probeKey = new ConverterKey(from, to); ConverterStub probeStub = this.converters.get(probeKey); if (probeStub != null) { // This was a probed find, so cache this value this.converters.put(key, probeStub); return (T) probeStub.convert(value, toType); } } } throw new UnsupportedConversionException(value, toType); } private List<Class<?>> getSuperclassesAndInterfaces(Class<?> type) { List<Class<?>> possible = new ArrayList<Class<?>>(); for (Class<?> current = type; current != null; current = current.getSuperclass()) { possible.add(current); possible.addAll(Copy.list(current.getInterfaces())); } return possible; } private static class ConverterKey { private final Class<?> from; private final Class<?> to; private ConverterKey(Class<?> from, Class<?> to) { this.from = from; this.to = to; } public boolean equals(Object other) { return this.from.equals(((ConverterKey) other).from) && this.to.equals(((ConverterKey) other).to); } public int hashCode() { return this.from.hashCode() + this.to.hashCode(); } public String toString() { return this.from.getSimpleName() + " -> " + this.to.getSimpleName(); } } private interface ConverterStub { abstract Object convert(Object value, Class<?> toType); } private static class ConverterStubOne<T, U> implements ConverterStub { private Converter<T, U> converter; public ConverterStubOne(Converter<T, U> converter) { this.converter = converter; } public Object convert(Object value, Class<?> toType) { return this.converter.convertOneToTwo((T) value, (Class<? extends U>) toType); } } private static class ConverterStubTwo<T, U> implements ConverterStub { private Converter<T, U> converter; public ConverterStubTwo(Converter<T, U> converter) { this.converter = converter; } public Object convert(Object value, Class<?> toType) { return this.converter.convertTwoToOne((U) value, (Class<? extends T>) toType); } } private static class NoopConverterStub implements ConverterStub { public Object convert(Object value, Class<?> toType) { return value; } } }