Skip to content

Commit

Permalink
Allow specifying custom converters in DefaultDecoder
Browse files Browse the repository at this point in the history
Add a factory method to DefaultDecoder, newCustomDecoder, which allows users to specify
custom TypeConverter factories; the existing default converter factories still get
installed when the factory method is used, but they are added after the supplied
custom factories.
  • Loading branch information
kilink committed Feb 13, 2024
1 parent 2446891 commit 8cc14ec
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
Expand All @@ -43,12 +46,29 @@ public class DefaultDecoder implements Decoder, TypeConverter.Registry {
public static final DefaultDecoder INSTANCE = new DefaultDecoder();

private DefaultDecoder() {
this(Collections.emptyList());
}

private DefaultDecoder(Collection<? extends TypeConverter.Factory> typeConverterFactories) {
factories.addAll(typeConverterFactories);
factories.add(DefaultTypeConverterFactory.INSTANCE);
factories.add(DefaultCollectionsTypeConverterFactory.INSTANCE);
factories.add(ArrayTypeConverterFactory.INSTANCE);
factories.add(EnumTypeConverterFactory.INSTANCE);
}

/**
* Create a new {@code DefaultDecoder} with the supplied {@code TypeConverter.Factory} instances installed.
* The default converter factories will still be registered, but will be installed AFTER any custom ones,
* giving callers the opportunity to override the behavior of the default converters.
*
* @param customTypeConverterFactories the collection of converter factories to use for this decoder
*/
public static DefaultDecoder newCustomDecoder(Collection<? extends TypeConverter.Factory> customTypeConverterFactories) {
Objects.requireNonNull(customTypeConverterFactories, "customTypeConverterFactories == null");
return customTypeConverterFactories.isEmpty() ? DefaultDecoder.INSTANCE : new DefaultDecoder(customTypeConverterFactories);
}

@Override
public <T> T decode(Class<T> type, String encoded) {
return decode((Type)type, encoded);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.netflix.archaius;


import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
Expand All @@ -29,12 +28,15 @@
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.BitSet;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.netflix.archaius.api.TypeConverter;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.junit.Assert;
Expand Down Expand Up @@ -97,4 +99,25 @@ public void testTypeConverterRegistry() {
class Foo {}
Assert.assertFalse(DefaultDecoder.INSTANCE.get(Foo.class).isPresent());
}

@Test
public void testCustomTypeConverters() {
TypeConverter<String> stringConverter = String::toUpperCase;
TypeConverter<Long> longConverter = value -> Long.parseLong(value) * 2;
TypeConverter.Factory factory = (type, registry) -> {
if ((type instanceof Class<?> && ((Class<?>) type).isAssignableFrom(String.class))) {
return Optional.of(stringConverter);
} else if (type.equals(Long.class)) { // override default converter
return Optional.of(longConverter);
}
return Optional.empty();
};
DefaultDecoder decoder = DefaultDecoder.newCustomDecoder(Collections.singletonList(factory));
Assert.assertEquals("FOO", decoder.decode(CharSequence.class, "foo"));
Assert.assertEquals("FOO", decoder.decode(String.class, "foo"));
// default is overridden
Assert.assertEquals(6L, decoder.decode(Long.class, "3").longValue());
// default converter is used
Assert.assertEquals(3, decoder.decode(Integer.class, "3").intValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -593,31 +593,18 @@ interface CustomObject {
@Test
public void testNestedInterfaceWithCustomDecoder() {
TypeConverter<ConfigWithNestedInterface.CustomObject> customObjectTypeConverter = value -> value::toUpperCase;

final class CustomDecoder implements Decoder, TypeConverter.Registry {
@Override
public <T> T decode(Class<T> type, String encoded) {
if (type.equals(ConfigWithNestedInterface.CustomObject.class)) {
@SuppressWarnings("unchecked")
T converted = (T) customObjectTypeConverter.convert(encoded);
return converted;
}
return DefaultDecoder.INSTANCE.decode(type, encoded);
}

@Override
public Optional<TypeConverter<?>> get(Type type) {
if (type.equals(ConfigWithNestedInterface.CustomObject.class)) {
return Optional.of(customObjectTypeConverter);
}
return DefaultDecoder.INSTANCE.get(type);
TypeConverter.Factory customTypeConverterFactory = (type, registry) -> {
if (type.equals(ConfigWithNestedInterface.CustomObject.class)) {
return Optional.of(customObjectTypeConverter);
}
}
return Optional.empty();
};
DefaultDecoder customDecoder = DefaultDecoder.newCustomDecoder(Collections.singletonList(customTypeConverterFactory));
Config config = MapConfig.builder()
.put("intValue", "5")
.put("customValue", "blah")
.build();
config.setDecoder(new CustomDecoder());
config.setDecoder(customDecoder);
ConfigProxyFactory proxyFactory = new ConfigProxyFactory(config, config.getDecoder(), DefaultPropertyFactory.from(config));

ConfigWithNestedInterface proxy = proxyFactory.newProxy(ConfigWithNestedInterface.class);
Expand Down

0 comments on commit 8cc14ec

Please sign in to comment.