Skip to content

Commit 66dce02

Browse files
committed
Merge branch '2.10'
2 parents 18991f7 + f834d6f commit 66dce02

File tree

10 files changed

+393
-93
lines changed

10 files changed

+393
-93
lines changed

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/DecimalUtils.java

+45-8
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,34 @@
1717
package com.fasterxml.jackson.datatype.jsr310;
1818

1919
import java.math.BigDecimal;
20+
import java.util.function.BiFunction;
2021

2122
/**
2223
* Utilities to aid in the translation of decimal types to/from multiple parts.
2324
*
2425
* @author Nick Williams
25-
* @since 2.2.0
26+
* @since 2.2
2627
*/
2728
public final class DecimalUtils
2829
{
2930
private static final BigDecimal ONE_BILLION = new BigDecimal(1_000_000_000L);
3031

31-
private DecimalUtils()
32-
{
33-
throw new RuntimeException("DecimalUtils cannot be instantiated.");
34-
}
32+
private DecimalUtils() { }
3533

3634
public static String toDecimal(long seconds, int nanoseconds)
3735
{
3836
StringBuilder sb = new StringBuilder(20)
3937
.append(seconds)
4038
.append('.');
4139
// 14-Mar-2016, tatu: Although we do not yet (with 2.7) trim trailing zeroes,
42-
// for general case,
40+
// for general case,
4341
if (nanoseconds == 0L) {
4442
// !!! TODO: 14-Mar-2016, tatu: as per [datatype-jsr310], should trim
4543
// trailing zeroes
4644
if (seconds == 0L) {
4745
return "0.0";
4846
}
49-
47+
5048
// sb.append('0');
5149
sb.append("000000000");
5250
} else {
@@ -92,12 +90,51 @@ public static BigDecimal toBigDecimal(long seconds, int nanoseconds)
9290
}
9391
return new BigDecimal(toDecimal(seconds, nanoseconds));
9492
}
95-
93+
94+
/**
95+
* @Deprecated due to potential unbounded latency on some JRE releases.
96+
*/
9697
public static int extractNanosecondDecimal(BigDecimal value, long integer)
9798
{
9899
// !!! 14-Mar-2016, tatu: Somewhat inefficient; should replace with functionally
99100
// equivalent code that just subtracts integral part? (or, measure and show
100101
// there's no difference and do nothing... )
101102
return value.subtract(new BigDecimal(integer)).multiply(ONE_BILLION).intValue();
102103
}
104+
105+
106+
/**
107+
* Extracts the seconds and nanoseconds component of {@code seconds} as {@code long} and {@code int}
108+
* values, passing them to the given converter. The implementation avoids latency issues present
109+
* on some JRE releases.
110+
*
111+
* @since 2.9.8
112+
*/
113+
public static <T> T extractSecondsAndNanos(BigDecimal seconds, BiFunction<Long, Integer, T> convert)
114+
{
115+
// Complexity is here to workaround unbounded latency in some BigDecimal operations.
116+
// https://github.com/FasterXML/jackson-databind/issues/2141
117+
118+
long secondsOnly;
119+
int nanosOnly;
120+
121+
BigDecimal nanoseconds = seconds.scaleByPowerOfTen(9);
122+
if (nanoseconds.precision() - nanoseconds.scale() <= 0) {
123+
// There are no non-zero digits to the left of the decimal point.
124+
// This protects against very negative exponents.
125+
secondsOnly = nanosOnly = 0;
126+
}
127+
else if (seconds.scale() < -63) {
128+
// There would be no low-order bits once we chop to a long.
129+
// This protects against very positive exponents.
130+
secondsOnly = nanosOnly = 0;
131+
}
132+
else {
133+
// Now we know that seconds has reasonable scale, we can safely chop it apart.
134+
secondsOnly = seconds.longValue();
135+
nanosOnly = nanoseconds.subtract(new BigDecimal(secondsOnly).scaleByPowerOfTen(9)).intValue();
136+
}
137+
138+
return convert.apply(secondsOnly, nanosOnly);
139+
}
103140
}

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.java

+3-29
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,7 @@
1616

1717
package com.fasterxml.jackson.datatype.jsr310;
1818

19-
import java.time.Duration;
20-
import java.time.Instant;
21-
import java.time.LocalDate;
22-
import java.time.LocalDateTime;
23-
import java.time.LocalTime;
24-
import java.time.MonthDay;
25-
import java.time.OffsetDateTime;
26-
import java.time.OffsetTime;
27-
import java.time.Period;
28-
import java.time.Year;
29-
import java.time.YearMonth;
30-
import java.time.ZoneId;
31-
import java.time.ZoneOffset;
32-
import java.time.ZonedDateTime;
19+
import java.time.*;
3320

3421
import com.fasterxml.jackson.databind.BeanDescription;
3522
import com.fasterxml.jackson.databind.DeserializationConfig;
@@ -52,20 +39,7 @@
5239
import com.fasterxml.jackson.datatype.jsr310.deser.OffsetTimeDeserializer;
5340
import com.fasterxml.jackson.datatype.jsr310.deser.YearDeserializer;
5441
import com.fasterxml.jackson.datatype.jsr310.deser.YearMonthDeserializer;
55-
import com.fasterxml.jackson.datatype.jsr310.deser.key.DurationKeyDeserializer;
56-
import com.fasterxml.jackson.datatype.jsr310.deser.key.InstantKeyDeserializer;
57-
import com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateKeyDeserializer;
58-
import com.fasterxml.jackson.datatype.jsr310.deser.key.LocalDateTimeKeyDeserializer;
59-
import com.fasterxml.jackson.datatype.jsr310.deser.key.LocalTimeKeyDeserializer;
60-
import com.fasterxml.jackson.datatype.jsr310.deser.key.MonthDayKeyDeserializer;
61-
import com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetDateTimeKeyDeserializer;
62-
import com.fasterxml.jackson.datatype.jsr310.deser.key.OffsetTimeKeyDeserializer;
63-
import com.fasterxml.jackson.datatype.jsr310.deser.key.PeriodKeyDeserializer;
64-
import com.fasterxml.jackson.datatype.jsr310.deser.key.YearKeyDeserializer;
65-
import com.fasterxml.jackson.datatype.jsr310.deser.key.YearMothKeyDeserializer;
66-
import com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneIdKeyDeserializer;
67-
import com.fasterxml.jackson.datatype.jsr310.deser.key.ZoneOffsetKeyDeserializer;
68-
import com.fasterxml.jackson.datatype.jsr310.deser.key.ZonedDateTimeKeyDeserializer;
42+
import com.fasterxml.jackson.datatype.jsr310.deser.key.*;
6943
import com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer;
7044
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
7145
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
@@ -191,7 +165,7 @@ public JavaTimeModule()
191165
addKeyDeserializer(OffsetTime.class, OffsetTimeKeyDeserializer.INSTANCE);
192166
addKeyDeserializer(Period.class, PeriodKeyDeserializer.INSTANCE);
193167
addKeyDeserializer(Year.class, YearKeyDeserializer.INSTANCE);
194-
addKeyDeserializer(YearMonth.class, YearMothKeyDeserializer.INSTANCE);
168+
addKeyDeserializer(YearMonth.class, YearMonthKeyDeserializer.INSTANCE);
195169
addKeyDeserializer(ZonedDateTime.class, ZonedDateTimeKeyDeserializer.INSTANCE);
196170
addKeyDeserializer(ZoneId.class, ZoneIdKeyDeserializer.INSTANCE);
197171
addKeyDeserializer(ZoneOffset.class, ZoneOffsetKeyDeserializer.INSTANCE);

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t
5252
{
5353
case JsonTokenId.ID_NUMBER_FLOAT:
5454
BigDecimal value = parser.getDecimalValue();
55-
long seconds = value.longValue();
56-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
57-
return Duration.ofSeconds(seconds, nanoseconds);
55+
return DecimalUtils.extractSecondsAndNanos(value, Duration::ofSeconds);
5856

5957
case JsonTokenId.ID_NUMBER_INT:
6058
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) {

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java

+9-10
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public class InstantDeserializer<T extends Temporal>
9191
protected final Function<FromDecimalArguments, T> fromNanoseconds;
9292

9393
protected final Function<TemporalAccessor, T> parsedToValue;
94-
94+
9595
protected final BiFunction<T, ZoneId, T> adjust;
9696

9797
/**
@@ -150,7 +150,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToConte
150150
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
151151
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
152152
}
153-
153+
154154
@Override
155155
protected JsonDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
156156
if (dtf == _formatter) {
@@ -218,7 +218,7 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I
218218
// 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
219219
// values quite easily
220220
return (T) parser.getEmbeddedObject();
221-
221+
222222
case JsonTokenId.ID_START_ARRAY:
223223
return _deserializeFromArray(parser, context);
224224
}
@@ -265,7 +265,7 @@ protected int _countPeriods(String str)
265265
}
266266
return commas;
267267
}
268-
268+
269269
protected T _fromLong(DeserializationContext context, long timestamp)
270270
{
271271
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
@@ -276,15 +276,14 @@ protected T _fromLong(DeserializationContext context, long timestamp)
276276
return fromMilliseconds.apply(new FromIntegerArguments(
277277
timestamp, this.getZone(context)));
278278
}
279-
279+
280280
protected T _fromDecimal(DeserializationContext context, BigDecimal value)
281281
{
282-
long seconds = value.longValue();
283-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
284-
return fromNanoseconds.apply(new FromDecimalArguments(
285-
seconds, nanoseconds, getZone(context)));
282+
FromDecimalArguments args =
283+
DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)));
284+
return fromNanoseconds.apply(args);
286285
}
287-
286+
288287
private ZoneId getZone(DeserializationContext context)
289288
{
290289
// Instants are always in UTC, so don't waste compute cycles

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/key/YearMothKeyDeserializer.java renamed to datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/key/YearMonthKeyDeserializer.java

+3-6
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212

1313
import com.fasterxml.jackson.databind.DeserializationContext;
1414

15-
public class YearMothKeyDeserializer extends Jsr310KeyDeserializer {
16-
17-
public static final YearMothKeyDeserializer INSTANCE = new YearMothKeyDeserializer();
15+
public class YearMonthKeyDeserializer extends Jsr310KeyDeserializer {
16+
public static final YearMonthKeyDeserializer INSTANCE = new YearMonthKeyDeserializer();
1817

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

26-
private YearMothKeyDeserializer() {
27-
// singleton
28-
}
25+
private YearMonthKeyDeserializer() { } // singleton
2926

3027
@Override
3128
protected YearMonth deserialize(String key, DeserializationContext ctxt) throws IOException {

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDecimalUtils.java

+74-34
Original file line numberDiff line numberDiff line change
@@ -29,75 +29,115 @@ public void testToDecimal01()
2929
"19827342231.999888000", decimal);
3030
}
3131

32+
33+
34+
private void checkExtractNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal)
35+
{
36+
long seconds = decimal.longValue();
37+
assertEquals("The second part is not correct.", expectedSeconds, seconds);
38+
39+
int nanoseconds = DecimalUtils.extractNanosecondDecimal(decimal, seconds);
40+
assertEquals("The nanosecond part is not correct.", expectedNanos, nanoseconds);
41+
}
42+
3243
@Test
3344
public void testExtractNanosecondDecimal01()
3445
{
3546
BigDecimal value = new BigDecimal("0");
36-
37-
long seconds = value.longValue();
38-
assertEquals("The second part is not correct.", 0L, seconds);
39-
40-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
41-
assertEquals("The nanosecond part is not correct.", 0, nanoseconds);
47+
checkExtractNanos(0L, 0, value);
4248
}
4349

4450
@Test
4551
public void testExtractNanosecondDecimal02()
4652
{
4753
BigDecimal value = new BigDecimal("15.000000072");
48-
49-
long seconds = value.longValue();
50-
assertEquals("The second part is not correct.", 15L, seconds);
51-
52-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
53-
assertEquals("The nanosecond part is not correct.", 72, nanoseconds);
54+
checkExtractNanos(15L, 72, value);
5455
}
5556

5657
@Test
5758
public void testExtractNanosecondDecimal03()
5859
{
5960
BigDecimal value = new BigDecimal("15.72");
60-
61-
long seconds = value.longValue();
62-
assertEquals("The second part is not correct.", 15L, seconds);
63-
64-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
65-
assertEquals("The nanosecond part is not correct.", 720000000, nanoseconds);
61+
checkExtractNanos(15L, 720000000, value);
6662
}
6763

6864
@Test
6965
public void testExtractNanosecondDecimal04()
7066
{
7167
BigDecimal value = new BigDecimal("19827342231.192837465");
72-
73-
long seconds = value.longValue();
74-
assertEquals("The second part is not correct.", 19827342231L, seconds);
75-
76-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
77-
assertEquals("The nanosecond part is not correct.", 192837465, nanoseconds);
68+
checkExtractNanos(19827342231L, 192837465, value);
7869
}
7970

8071
@Test
8172
public void testExtractNanosecondDecimal05()
8273
{
8374
BigDecimal value = new BigDecimal("19827342231");
84-
85-
long seconds = value.longValue();
86-
assertEquals("The second part is not correct.", 19827342231L, seconds);
87-
88-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
89-
assertEquals("The nanosecond part is not correct.", 0, nanoseconds);
75+
checkExtractNanos(19827342231L, 0, value);
9076
}
9177

9278
@Test
9379
public void testExtractNanosecondDecimal06()
9480
{
9581
BigDecimal value = new BigDecimal("19827342231.999999999");
82+
checkExtractNanos(19827342231L, 999999999, value);
83+
}
9684

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

100-
int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds);
101-
assertEquals("The nanosecond part is not correct.", 999999999, nanoseconds);
86+
private void checkExtractSecondsAndNanos(long expectedSeconds, int expectedNanos, BigDecimal decimal)
87+
{
88+
DecimalUtils.extractSecondsAndNanos(decimal, (Long s, Integer ns) -> {
89+
assertEquals("The second part is not correct.", expectedSeconds, s.longValue());
90+
assertEquals("The nanosecond part is not correct.", expectedNanos, ns.intValue());
91+
return null;
92+
});
93+
}
94+
95+
@Test
96+
public void testExtractSecondsAndNanos01()
97+
{
98+
BigDecimal value = new BigDecimal("0");
99+
checkExtractSecondsAndNanos(0L, 0, value);
100+
}
101+
102+
@Test
103+
public void testExtractSecondsAndNanos02()
104+
{
105+
BigDecimal value = new BigDecimal("15.000000072");
106+
checkExtractSecondsAndNanos(15L, 72, value);
107+
}
108+
109+
@Test
110+
public void testExtractSecondsAndNanos03()
111+
{
112+
BigDecimal value = new BigDecimal("15.72");
113+
checkExtractSecondsAndNanos(15L, 720000000, value);
114+
}
115+
116+
@Test
117+
public void testExtractSecondsAndNanos04()
118+
{
119+
BigDecimal value = new BigDecimal("19827342231.192837465");
120+
checkExtractSecondsAndNanos(19827342231L, 192837465, value);
121+
}
122+
123+
@Test
124+
public void testExtractSecondsAndNanos05()
125+
{
126+
BigDecimal value = new BigDecimal("19827342231");
127+
checkExtractSecondsAndNanos(19827342231L, 0, value);
128+
}
129+
130+
@Test
131+
public void testExtractSecondsAndNanos06()
132+
{
133+
BigDecimal value = new BigDecimal("19827342231.999999999");
134+
checkExtractSecondsAndNanos(19827342231L, 999999999, value);
135+
}
136+
137+
@Test(timeout = 100)
138+
public void testExtractSecondsAndNanos07()
139+
{
140+
BigDecimal value = new BigDecimal("1e10000000");
141+
checkExtractSecondsAndNanos(0L, 0, value);
102142
}
103143
}

0 commit comments

Comments
 (0)