package com.fasterxml.jackson.datatype.jsr310.deser;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.JsonTokenId;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Pattern;
public class InstantDeserializer<T extends Temporal>
extends JSR310DateTimeDeserializerBase<T>
{
private static final long serialVersionUID = 1L;
private static final Pattern ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX = Pattern.compile("\\+00:?(00)?$");
public static final InstantDeserializer<Instant> INSTANT = new InstantDeserializer<>(
Instant.class, DateTimeFormatter.ISO_INSTANT,
Instant::from,
a -> Instant.ofEpochMilli(a.value),
a -> Instant.ofEpochSecond(a.integer, a.fraction),
null,
true
);
public static final InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new InstantDeserializer<>(
OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
OffsetDateTime::from,
a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
(d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
true
);
public static final InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new InstantDeserializer<>(
ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
ZonedDateTime::from,
a -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
ZonedDateTime::withZoneSameInstant,
false
);
protected final Function<FromIntegerArguments, T> fromMilliseconds;
protected final Function<FromDecimalArguments, T> fromNanoseconds;
protected final Function<TemporalAccessor, T> parsedToValue;
protected final BiFunction<T, ZoneId, T> adjust;
protected final boolean replaceZeroOffsetAsZ;
protected final Boolean _adjustToContextTZOverride;
protected InstantDeserializer(Class<T> supportedType,
DateTimeFormatter formatter,
Function<TemporalAccessor, T> parsedToValue,
Function<FromIntegerArguments, T> fromMilliseconds,
Function<FromDecimalArguments, T> fromNanoseconds,
BiFunction<T, ZoneId, T> adjust,
boolean replaceZeroOffsetAsZ)
{
super(supportedType, formatter);
this.parsedToValue = parsedToValue;
this.fromMilliseconds = fromMilliseconds;
this.fromNanoseconds = fromNanoseconds;
this.adjust = adjust == null ? ((d, z) -> d) : adjust;
this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ;
_adjustToContextTZOverride = null;
}
@SuppressWarnings("unchecked")
protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f)
{
super((Class<T>) base.handledType(), f);
parsedToValue = base.parsedToValue;
fromMilliseconds = base.fromMilliseconds;
fromNanoseconds = base.fromNanoseconds;
adjust = base.adjust;
replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
_adjustToContextTZOverride = base._adjustToContextTZOverride;
}
@SuppressWarnings("unchecked")
protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToContextTimezoneOverride)
{
super((Class<T>) base.handledType(), base._formatter);
parsedToValue = base.parsedToValue;
fromMilliseconds = base.fromMilliseconds;
fromNanoseconds = base.fromNanoseconds;
adjust = base.adjust;
replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
_adjustToContextTZOverride = adjustToContextTimezoneOverride;
}
@Override
protected JsonDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
if (dtf == _formatter) {
return this;
}
return new InstantDeserializer<T>(this, dtf);
}
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
switch (parser.getCurrentTokenId())
{
case JsonTokenId.ID_NUMBER_FLOAT:
return _fromDecimal(context, parser.getDecimalValue());
case JsonTokenId.ID_NUMBER_INT:
return _fromLong(context, parser.getLongValue());
case JsonTokenId.ID_STRING:
{
String string = parser.getText().trim();
if (string.length() == 0) {
return null;
}
if (_formatter == DateTimeFormatter.ISO_INSTANT ||
_formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME ||
_formatter == DateTimeFormatter.ISO_ZONED_DATE_TIME) {
int dots = _countPeriods(string);
if (dots >= 0) {
try {
if (dots == 0) {
return _fromLong(context, Long.parseLong(string));
}
if (dots == 1) {
return _fromDecimal(context, new BigDecimal(string));
}
} catch (NumberFormatException e) {
}
}
string = replaceZeroOffsetAsZIfNecessary(string);
}
T value;
try {
TemporalAccessor acc = _formatter.parse(string);
value = parsedToValue.apply(acc);
if (shouldAdjustToContextTimezone(context)) {
return adjust.apply(value, this.getZone(context));
}
} catch (DateTimeException e) {
value = _rethrowDateTimeException(parser, context, e, string);
}
return value;
}
case JsonTokenId.ID_EMBEDDED_OBJECT:
return (T) parser.getEmbeddedObject();
case JsonTokenId.ID_START_ARRAY:
return _deserializeFromArray(parser, context);
}
return _reportWrongToken(parser, context, JsonToken.VALUE_STRING,
JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
}
@SuppressWarnings("unchecked")
@Override
public JsonDeserializer<T> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
InstantDeserializer<T> deserializer =
(InstantDeserializer<T>)super.createContextual(ctxt, property);
if (deserializer != this) {
JsonFormat.Value val = findFormatOverrides(ctxt, property, handledType());
if (val != null) {
return new InstantDeserializer<>(deserializer, val.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE));
}
}
return this;
}
protected boolean shouldAdjustToContextTimezone(DeserializationContext context) {
return (_adjustToContextTZOverride != null) ? _adjustToContextTZOverride :
context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
}
protected int _countPeriods(String str)
{
int commas = 0;
for (int i = 0, end = str.length(); i < end; ++i) {
int ch = str.charAt(i);
if (ch < '0' || ch > '9') {
if (ch == '.') {
++commas;
} else {
return -1;
}
}
}
return commas;
}
protected T _fromLong(DeserializationContext context, long timestamp)
{
if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
return fromNanoseconds.apply(new FromDecimalArguments(
timestamp, 0, this.getZone(context)
));
}
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)));
}
private ZoneId getZone(DeserializationContext context)
{
return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId();
}
private String replaceZeroOffsetAsZIfNecessary(String text)
{
if (replaceZeroOffsetAsZ) {
return ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX.matcher(text).replaceFirst("Z");
}
return text;
}
public static class FromIntegerArguments
{
public final long value;
public final ZoneId zoneId;
private FromIntegerArguments(long value, ZoneId zoneId)
{
this.value = value;
this.zoneId = zoneId;
}
}
public static class FromDecimalArguments
{
public final long integer;
public final int fraction;
public final ZoneId zoneId;
private FromDecimalArguments(long integer, int fraction, ZoneId zoneId)
{
this.integer = integer;
this.fraction = fraction;
this.zoneId = zoneId;
}
}
}