Skip to content

Commit

Permalink
Merge pull request #429 from tcellucci/decoder_enhancements
Browse files Browse the repository at this point in the history
add handling for java 8 time classes and miscellaneous others
  • Loading branch information
tcellucci authored Aug 8, 2016
2 parents 268fbc3 + 81c29b0 commit 63e01b1
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 40 deletions.
108 changes: 71 additions & 37 deletions archaius2-core/src/main/java/com/netflix/archaius/DefaultDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,66 +15,100 @@
*/
package com.netflix.archaius;

import com.netflix.archaius.api.Decoder;
import com.netflix.archaius.exceptions.ParseException;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.BitSet;
import java.util.Currency;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

import javax.inject.Singleton;
import javax.xml.bind.DatatypeConverter;

import com.netflix.archaius.api.Decoder;
import com.netflix.archaius.exceptions.ParseException;

/**
* @author Spencer Gibb
*/
@Singleton
public class DefaultDecoder implements Decoder {
private Map<Class<?>, Function<String, ?>> decoderRegistry;

public static DefaultDecoder INSTANCE = new DefaultDecoder();

{
decoderRegistry = new IdentityHashMap<>(75);
decoderRegistry.put(String.class, v->v);
decoderRegistry.put(boolean.class, v->{
if (v.equalsIgnoreCase("true") || v.equalsIgnoreCase("yes") || v.equalsIgnoreCase("on")) {
return Boolean.TRUE;
}
else if (v.equalsIgnoreCase("false") || v.equalsIgnoreCase("no") || v.equalsIgnoreCase("off")) {
return Boolean.FALSE;
}
throw new ParseException("Error parsing value '" + v, new Exception("Expected one of [true, yes, on, false, no, off]"));

});
decoderRegistry.put(Boolean.class, decoderRegistry.get(boolean.class));
decoderRegistry.put(Integer.class, Integer::valueOf);
decoderRegistry.put(int.class, Integer::valueOf);
decoderRegistry.put(long.class, Long::valueOf);
decoderRegistry.put(Long.class, Long::valueOf);
decoderRegistry.put(short.class, Short::valueOf);
decoderRegistry.put(Short.class, Short::valueOf);
decoderRegistry.put(byte.class, Byte::valueOf);
decoderRegistry.put(Byte.class, Byte::valueOf);
decoderRegistry.put(double.class, Double::valueOf);
decoderRegistry.put(Double.class, Double::valueOf);
decoderRegistry.put(float.class, Float::valueOf);
decoderRegistry.put(Float.class, Float::valueOf);
decoderRegistry.put(BigInteger.class, BigInteger::new);
decoderRegistry.put(BigDecimal.class, BigDecimal::new);
decoderRegistry.put(AtomicInteger.class, s->new AtomicInteger(Integer.parseInt(s)));
decoderRegistry.put(AtomicLong.class, s->new AtomicLong(Long.parseLong(s)));
decoderRegistry.put(Duration.class, Duration::parse);
decoderRegistry.put(Period.class, Period::parse);
decoderRegistry.put(LocalDateTime.class, LocalDateTime::parse);
decoderRegistry.put(LocalDate.class, LocalDate::parse);
decoderRegistry.put(LocalTime.class, LocalTime::parse);
decoderRegistry.put(OffsetDateTime.class, OffsetDateTime::parse);
decoderRegistry.put(OffsetTime.class, OffsetTime::parse);
decoderRegistry.put(ZonedDateTime.class, ZonedDateTime::parse);
decoderRegistry.put(Instant.class, v->Instant.from(OffsetDateTime.parse(v)));
decoderRegistry.put(Date.class, v->new Date(Long.parseLong(v)));
decoderRegistry.put(Currency.class, Currency::getInstance);
decoderRegistry.put(BitSet.class, v->BitSet.valueOf(DatatypeConverter.parseHexBinary(v)));
}


@SuppressWarnings("unchecked")
@Override
public <T> T decode(Class<T> type, String encoded) {
if (encoded == null) {
return null;
}
// Try primitives first
if (type.equals(String.class)) {
return (T) encoded;
}
else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
if (encoded.equalsIgnoreCase("true") || encoded.equalsIgnoreCase("yes") || encoded.equalsIgnoreCase("on")) {
return (T) Boolean.TRUE;
}
else if (encoded.equalsIgnoreCase("false") || encoded.equalsIgnoreCase("no") || encoded.equalsIgnoreCase("off")) {
return (T) Boolean.FALSE;
}
throw new ParseException("Error parsing value '" + encoded, new Exception("Expected one of [true, yes, on, false, no, off]"));
}
else if (type.equals(int.class) || type.equals(Integer.class)) {
return (T) Integer.valueOf(encoded);
}
else if (type.equals(long.class) || type.equals(Long.class)) {
return (T) Long.valueOf(encoded);
}
else if (type.equals(short.class) || type.equals(Short.class)) {
return (T) Short.valueOf(encoded);
}
else if (type.equals(double.class) || type.equals(Double.class)) {
return (T) Double.valueOf(encoded);
}
else if (type.equals(float.class) || type.equals(Float.class)) {
return (T) Float.valueOf(encoded);
}
else if (type.equals(BigInteger.class)) {
return (T) new BigInteger(encoded);
}
else if (type.equals(BigDecimal.class)) {
return (T) new BigDecimal(encoded);
if (decoderRegistry.containsKey(type)) {
return (T)decoderRegistry.get(type).apply(encoded);
}
else if (type.isArray()) {

if (type.isArray()) {
String[] elements = encoded.split(",");
T[] ar = (T[]) Array.newInstance(type.getComponentType(), elements.length);
for (int i = 0; i < elements.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,75 @@
package com.netflix.archaius;


import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZonedDateTime;
import java.util.BitSet;
import java.util.Currency;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import javax.xml.bind.DatatypeConverter;

import org.junit.Assert;
import org.junit.Test;

import com.netflix.archaius.DefaultDecoder;

public class DefaultDecoderTest {
@Test
public void testPrimitives() {
public void testJavaNumbers() {
DefaultDecoder decoder = new DefaultDecoder();

boolean flag = decoder.decode(boolean.class, "true");
Assert.assertEquals(true, flag);
int int_value = decoder.decode(int.class, "123");
Assert.assertEquals(123, int_value);

Assert.assertEquals(Byte.valueOf(Byte.MAX_VALUE), decoder.decode(Byte.class, String.valueOf(Byte.MAX_VALUE)));
Assert.assertEquals(Short.valueOf(Short.MAX_VALUE), decoder.decode(Short.class, String.valueOf(Short.MAX_VALUE)));
Assert.assertEquals(Long.valueOf(Long.MAX_VALUE), decoder.decode(Long.class, String.valueOf(Long.MAX_VALUE)));
Assert.assertEquals(Integer.valueOf(Integer.MAX_VALUE), decoder.decode(Integer.class, String.valueOf(Integer.MAX_VALUE)));
Assert.assertEquals(Float.valueOf(Float.MAX_VALUE), decoder.decode(Float.class, String.valueOf(Float.MAX_VALUE)));
Assert.assertEquals(Double.valueOf(Double.MAX_VALUE), decoder.decode(Double.class, String.valueOf(Double.MAX_VALUE)));
Assert.assertEquals(BigInteger.valueOf(Long.MAX_VALUE), decoder.decode(BigInteger.class, String.valueOf(Long.MAX_VALUE)));
Assert.assertEquals(BigDecimal.valueOf(Double.MAX_VALUE), decoder.decode(BigDecimal.class, String.valueOf(Double.MAX_VALUE)));
Assert.assertEquals(new AtomicInteger(Integer.MAX_VALUE).intValue(), decoder.decode(AtomicInteger.class, String.valueOf(Integer.MAX_VALUE)).intValue());
Assert.assertEquals(new AtomicLong(Long.MAX_VALUE).longValue(), decoder.decode(AtomicLong.class, String.valueOf(Long.MAX_VALUE)).longValue());
}

@Test
public void testJavaDateTime() {
DefaultDecoder decoder = new DefaultDecoder();

Assert.assertEquals(Duration.parse("PT20M30S"), decoder.decode(Duration.class, "PT20M30S"));
Assert.assertEquals(Period.of(1, 2, 25), decoder.decode(Period.class, "P1Y2M3W4D"));
Assert.assertEquals(OffsetDateTime.parse("2016-08-03T10:15:30+07:00"), decoder.decode(OffsetDateTime.class, "2016-08-03T10:15:30+07:00"));
Assert.assertEquals(OffsetTime.parse("10:15:30+18:00"), decoder.decode(OffsetTime.class, "10:15:30+18:00"));
Assert.assertEquals(ZonedDateTime.parse("2016-08-03T10:15:30+01:00[Europe/Paris]"), decoder.decode(ZonedDateTime.class, "2016-08-03T10:15:30+01:00[Europe/Paris]"));
Assert.assertEquals(LocalDateTime.parse("2016-08-03T10:15:30"), decoder.decode(LocalDateTime.class, "2016-08-03T10:15:30"));
Assert.assertEquals(LocalDate.parse("2016-08-03"), decoder.decode(LocalDate.class, "2016-08-03"));
Assert.assertEquals(LocalTime.parse("10:15:30"), decoder.decode(LocalTime.class, "10:15:30"));
Assert.assertEquals(Instant.from(OffsetDateTime.parse("2016-08-03T10:15:30+07:00")), decoder.decode(Instant.class, "2016-08-03T10:15:30+07:00"));
Date newDate = new Date();
Assert.assertEquals(newDate, decoder.decode(Date.class, String.valueOf(newDate.getTime())));
}

@Test
public void testJavaMiscellaneous() {
DefaultDecoder decoder = new DefaultDecoder();
Assert.assertEquals(Currency.getInstance("USD"), decoder.decode(Currency.class, "USD"));
Assert.assertEquals(BitSet.valueOf(DatatypeConverter.parseHexBinary("DEADBEEF00DEADBEEF")), decoder.decode(BitSet.class, "DEADBEEF00DEADBEEF"));
Assert.assertEquals("testString", decoder.decode(String.class, "testString"));
}

}
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ subprojects {
apply plugin: 'nebula.provided-base'
apply plugin: 'java'

sourceCompatibility = 1.7
targetCompatibility = 1.7
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
jcenter()
Expand Down

0 comments on commit 63e01b1

Please sign in to comment.