Skip to content

Commit

Permalink
Merge branch '2.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Oct 24, 2018
2 parents 18991f7 + f834d6f commit 66dce02
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,34 @@
package com.fasterxml.jackson.datatype.jsr310;

import java.math.BigDecimal;
import java.util.function.BiFunction;

/**
* Utilities to aid in the translation of decimal types to/from multiple parts.
*
* @author Nick Williams
* @since 2.2.0
* @since 2.2
*/
public final class DecimalUtils
{
private static final BigDecimal ONE_BILLION = new BigDecimal(1_000_000_000L);

private DecimalUtils()
{
throw new RuntimeException("DecimalUtils cannot be instantiated.");
}
private DecimalUtils() { }

public static String toDecimal(long seconds, int nanoseconds)
{
StringBuilder sb = new StringBuilder(20)
.append(seconds)
.append('.');
// 14-Mar-2016, tatu: Although we do not yet (with 2.7) trim trailing zeroes,
// for general case,
// for general case,
if (nanoseconds == 0L) {
// !!! TODO: 14-Mar-2016, tatu: as per [datatype-jsr310], should trim
// trailing zeroes
if (seconds == 0L) {
return "0.0";
}

// sb.append('0');
sb.append("000000000");
} else {
Expand Down Expand Up @@ -92,12 +90,51 @@ public static BigDecimal toBigDecimal(long seconds, int nanoseconds)
}
return new BigDecimal(toDecimal(seconds, nanoseconds));
}


/**
* @Deprecated due to potential unbounded latency on some JRE releases.
*/
public static int extractNanosecondDecimal(BigDecimal value, long integer)
{
// !!! 14-Mar-2016, tatu: Somewhat inefficient; should replace with functionally
// equivalent code that just subtracts integral part? (or, measure and show
// there's no difference and do nothing... )
return value.subtract(new BigDecimal(integer)).multiply(ONE_BILLION).intValue();
}


/**
* Extracts the seconds and nanoseconds component of {@code seconds} as {@code long} and {@code int}
* values, passing them to the given converter. The implementation avoids latency issues present
* on some JRE releases.
*
* @since 2.9.8
*/
public static <T> T extractSecondsAndNanos(BigDecimal seconds, BiFunction<Long, Integer, T> convert)
{
// Complexity is here to workaround unbounded latency in some BigDecimal operations.
// https://github.com/FasterXML/jackson-databind/issues/2141

long secondsOnly;
int nanosOnly;

BigDecimal nanoseconds = seconds.scaleByPowerOfTen(9);
if (nanoseconds.precision() - nanoseconds.scale() <= 0) {
// There are no non-zero digits to the left of the decimal point.
// This protects against very negative exponents.
secondsOnly = nanosOnly = 0;
}
else if (seconds.scale() < -63) {
// There would be no low-order bits once we chop to a long.
// This protects against very positive exponents.
secondsOnly = nanosOnly = 0;
}
else {
// Now we know that seconds has reasonable scale, we can safely chop it apart.
secondsOnly = seconds.longValue();
nanosOnly = nanoseconds.subtract(new BigDecimal(secondsOnly).scaleByPowerOfTen(9)).intValue();
}

return convert.apply(secondsOnly, nanosOnly);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,7 @@

package com.fasterxml.jackson.datatype.jsr310;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.*;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
Expand All @@ -52,20 +39,7 @@
import com.fasterxml.jackson.datatype.jsr310.deser.OffsetTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.YearDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.YearMonthDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.DurationKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.InstantKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateTimeKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.LocalTimeKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.MonthDayKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetDateTimeKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetTimeKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.PeriodKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.YearKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.YearMothKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneIdKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneOffsetKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.ZonedDateTimeKeyDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.key.*;
import com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
Expand Down Expand Up @@ -191,7 +165,7 @@ public JavaTimeModule()
addKeyDeserializer(OffsetTime.class, OffsetTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(Period.class, PeriodKeyDeserializer.INSTANCE);
addKeyDeserializer(Year.class, YearKeyDeserializer.INSTANCE);
addKeyDeserializer(YearMonth.class, YearMothKeyDeserializer.INSTANCE);
addKeyDeserializer(YearMonth.class, YearMonthKeyDeserializer.INSTANCE);
addKeyDeserializer(ZonedDateTime.class, ZonedDateTimeKeyDeserializer.INSTANCE);
addKeyDeserializer(ZoneId.class, ZoneIdKeyDeserializer.INSTANCE);
addKeyDeserializer(ZoneOffset.class, ZoneOffsetKeyDeserializer.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t
{
case JsonTokenId.ID_NUMBER_FLOAT:
BigDecimal value = parser.getDecimalValue();
long seconds = value.longValue();
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
return Duration.ofSeconds(seconds, nanoseconds);
return DecimalUtils.extractSecondsAndNanos(value, Duration::ofSeconds);

case JsonTokenId.ID_NUMBER_INT:
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public class InstantDeserializer<T extends Temporal>
protected final Function<FromDecimalArguments, T> fromNanoseconds;

protected final Function<TemporalAccessor, T> parsedToValue;

protected final BiFunction<T, ZoneId, T> adjust;

/**
Expand Down Expand Up @@ -150,7 +150,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToConte
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
}

@Override
protected JsonDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
if (dtf == _formatter) {
Expand Down Expand Up @@ -218,7 +218,7 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I
// 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
// values quite easily
return (T) parser.getEmbeddedObject();

case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(parser, context);
}
Expand Down Expand Up @@ -265,7 +265,7 @@ protected int _countPeriods(String str)
}
return commas;
}

protected T _fromLong(DeserializationContext context, long timestamp)
{
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
Expand All @@ -276,15 +276,14 @@ protected T _fromLong(DeserializationContext context, long timestamp)
return fromMilliseconds.apply(new FromIntegerArguments(
timestamp, this.getZone(context)));
}

protected T _fromDecimal(DeserializationContext context, BigDecimal value)
{
long seconds = value.longValue();
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
return fromNanoseconds.apply(new FromDecimalArguments(
seconds, nanoseconds, getZone(context)));
FromDecimalArguments args =
DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)));
return fromNanoseconds.apply(args);
}

private ZoneId getZone(DeserializationContext context)
{
// Instants are always in UTC, so don't waste compute cycles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@

import com.fasterxml.jackson.databind.DeserializationContext;

public class YearMothKeyDeserializer extends Jsr310KeyDeserializer {

public static final YearMothKeyDeserializer INSTANCE = new YearMothKeyDeserializer();
public class YearMonthKeyDeserializer extends Jsr310KeyDeserializer {
public static final YearMonthKeyDeserializer INSTANCE = new YearMonthKeyDeserializer();

// parser copied from YearMonth
private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
Expand All @@ -23,9 +22,7 @@ public class YearMothKeyDeserializer extends Jsr310KeyDeserializer {
.appendValue(MONTH_OF_YEAR, 2)
.toFormatter();

private YearMothKeyDeserializer() {
// singleton
}
private YearMonthKeyDeserializer() { } // singleton

@Override
protected YearMonth deserialize(String key, DeserializationContext ctxt) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,75 +29,115 @@ public void testToDecimal01()
"19827342231.999888000", decimal);
}



private void checkExtractNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal)
{
long seconds = decimal.longValue();
assertEquals("The second part is not correct.", expectedSeconds, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(decimal, seconds);
assertEquals("The nanosecond part is not correct.", expectedNanos, nanoseconds);
}

@Test
public void testExtractNanosecondDecimal01()
{
BigDecimal value = new BigDecimal("0");

long seconds = value.longValue();
assertEquals("The second part is not correct.", 0L, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
assertEquals("The nanosecond part is not correct.", 0, nanoseconds);
checkExtractNanos(0L, 0, value);
}

@Test
public void testExtractNanosecondDecimal02()
{
BigDecimal value = new BigDecimal("15.000000072");

long seconds = value.longValue();
assertEquals("The second part is not correct.", 15L, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
assertEquals("The nanosecond part is not correct.", 72, nanoseconds);
checkExtractNanos(15L, 72, value);
}

@Test
public void testExtractNanosecondDecimal03()
{
BigDecimal value = new BigDecimal("15.72");

long seconds = value.longValue();
assertEquals("The second part is not correct.", 15L, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
assertEquals("The nanosecond part is not correct.", 720000000, nanoseconds);
checkExtractNanos(15L, 720000000, value);
}

@Test
public void testExtractNanosecondDecimal04()
{
BigDecimal value = new BigDecimal("19827342231.192837465");

long seconds = value.longValue();
assertEquals("The second part is not correct.", 19827342231L, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
assertEquals("The nanosecond part is not correct.", 192837465, nanoseconds);
checkExtractNanos(19827342231L, 192837465, value);
}

@Test
public void testExtractNanosecondDecimal05()
{
BigDecimal value = new BigDecimal("19827342231");

long seconds = value.longValue();
assertEquals("The second part is not correct.", 19827342231L, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
assertEquals("The nanosecond part is not correct.", 0, nanoseconds);
checkExtractNanos(19827342231L, 0, value);
}

@Test
public void testExtractNanosecondDecimal06()
{
BigDecimal value = new BigDecimal("19827342231.999999999");
checkExtractNanos(19827342231L, 999999999, value);
}

long seconds = value.longValue();
assertEquals("The second part is not correct.", 19827342231L, seconds);

int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
assertEquals("The nanosecond part is not correct.", 999999999, nanoseconds);
private void checkExtractSecondsAndNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal)
{
DecimalUtils.extractSecondsAndNanos(decimal, (Long s, Integer ns) -> {
assertEquals("The second part is not correct.", expectedSeconds, s.longValue());
assertEquals("The nanosecond part is not correct.", expectedNanos, ns.intValue());
return null;
});
}

@Test
public void testExtractSecondsAndNanos01()
{
BigDecimal value = new BigDecimal("0");
checkExtractSecondsAndNanos(0L, 0, value);
}

@Test
public void testExtractSecondsAndNanos02()
{
BigDecimal value = new BigDecimal("15.000000072");
checkExtractSecondsAndNanos(15L, 72, value);
}

@Test
public void testExtractSecondsAndNanos03()
{
BigDecimal value = new BigDecimal("15.72");
checkExtractSecondsAndNanos(15L, 720000000, value);
}

@Test
public void testExtractSecondsAndNanos04()
{
BigDecimal value = new BigDecimal("19827342231.192837465");
checkExtractSecondsAndNanos(19827342231L, 192837465, value);
}

@Test
public void testExtractSecondsAndNanos05()
{
BigDecimal value = new BigDecimal("19827342231");
checkExtractSecondsAndNanos(19827342231L, 0, value);
}

@Test
public void testExtractSecondsAndNanos06()
{
BigDecimal value = new BigDecimal("19827342231.999999999");
checkExtractSecondsAndNanos(19827342231L, 999999999, value);
}

@Test(timeout = 100)
public void testExtractSecondsAndNanos07()
{
BigDecimal value = new BigDecimal("1e10000000");
checkExtractSecondsAndNanos(0L, 0, value);
}
}
Loading

0 comments on commit 66dce02

Please sign in to comment.