All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.prestosql.jdbc.$internal.jackson.datatype.jsr310.deser.InstantDeserializer Maven / Gradle / Ivy

There is a newer version: 350
Show newest version
/*
 * Copyright 2013 FasterXML.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */

package io.prestosql.jdbc.$internal.jackson.datatype.jsr310.deser;

import io.prestosql.jdbc.$internal.jackson.annotation.JsonFormat;
import io.prestosql.jdbc.$internal.jackson.core.JsonParser;
import io.prestosql.jdbc.$internal.jackson.core.JsonToken;
import io.prestosql.jdbc.$internal.jackson.core.JsonTokenId;
import io.prestosql.jdbc.$internal.jackson.databind.BeanProperty;
import io.prestosql.jdbc.$internal.jackson.databind.DeserializationContext;
import io.prestosql.jdbc.$internal.jackson.databind.DeserializationFeature;
import io.prestosql.jdbc.$internal.jackson.databind.JsonDeserializer;
import io.prestosql.jdbc.$internal.jackson.databind.JsonMappingException;
import io.prestosql.jdbc.$internal.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;

/**
 * Deserializer for Java 8 temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s.
 *
 * @author Nick Williams
 * @since 2.2
 */
public class InstantDeserializer
    extends JSR310DateTimeDeserializerBase
{
    private static final long serialVersionUID = 1L;

    /**
     * Constants used to check if the time offset is zero. See [jackson-modules-java8#18]
     *
     * @since 2.9.0
     */
    private static final Pattern ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX = Pattern.compile("\\+00:?(00)?$");

    public static final InstantDeserializer INSTANT = new InstantDeserializer<>(
            Instant.class, DateTimeFormatter.ISO_INSTANT,
            Instant::from,
            a -> Instant.ofEpochMilli(a.value),
            a -> Instant.ofEpochSecond(a.integer, a.fraction),
            null,
            true // yes, replace zero offset with Z
    );

    public static final InstantDeserializer 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 // yes, replace zero offset with Z
    );

    public static final InstantDeserializer 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 // keep zero offset and Z separate since zones explicitly supported
    );

    protected final Function fromMilliseconds;

    protected final Function fromNanoseconds;

    protected final Function parsedToValue;

    protected final BiFunction adjust;

    /**
     * In case of vanilla `Instant` we seem to need to translate "+0000 | +00:00 | +00"
     * timezone designator into plain "Z" for some reason; see
     * [jackson-modules-java8#18] for more info
     *
     * @since 2.9.0
     */
    protected final boolean replaceZeroOffsetAsZ;

    /**
     * Flag for JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE
     *
     * @since 2.8
     */
    protected final Boolean _adjustToContextTZOverride;

    protected InstantDeserializer(Class supportedType,
            DateTimeFormatter formatter,
            Function parsedToValue,
            Function fromMilliseconds,
            Function fromNanoseconds,
            BiFunction 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 base, DateTimeFormatter f)
    {
        super((Class) 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 base, Boolean adjustToContextTimezoneOverride)
    {
        super((Class) base.handledType(), base._formatter);
        parsedToValue = base.parsedToValue;
        fromMilliseconds = base.fromMilliseconds;
        fromNanoseconds = base.fromNanoseconds;
        adjust = base.adjust;
        replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
        _adjustToContextTZOverride = adjustToContextTimezoneOverride;
    }

    @Override
    protected InstantDeserializer withDateFormat(DateTimeFormatter dtf) {
        if (dtf == _formatter) {
            return this;
        }
        return new InstantDeserializer(this, dtf);
    }

    // !!! TODO: lenient vs strict?
    @Override
    protected InstantDeserializer withLeniency(Boolean leniency) {
        return this;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public T deserialize(JsonParser parser, DeserializationContext context) throws IOException
    {
        //NOTE: Timestamps contain no timezone info, and are always in configured TZ. Only
        //string values have to be adjusted to the configured TZ.
        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;
                }
                // only check for other parsing modes if we are using default formatter
                if (_formatter == DateTimeFormatter.ISO_INSTANT ||
                    _formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME ||
                    _formatter == DateTimeFormatter.ISO_ZONED_DATE_TIME) {
                    // 22-Jan-2016, [datatype-jsr310#16]: Allow quoted numbers too
                    int dots = _countPeriods(string);
                    if (dots >= 0) { // negative if not simple number
                        try {
                            if (dots == 0) {
                                return _fromLong(context, Long.parseLong(string));
                            }
                            if (dots == 1) {
                                return _fromDecimal(context, new BigDecimal(string));
                            }
                        } catch (NumberFormatException e) {
                            // fall through to default handling, to get error there
                        }
                    }

                    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 = _handleDateTimeException(context, e, string);
                }
                return value;
            }

            case JsonTokenId.ID_EMBEDDED_OBJECT:
                // 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);
        }
        return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING,
                JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
    }

    @SuppressWarnings("unchecked")
    @Override
    public JsonDeserializer createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException
    {
        InstantDeserializer deserializer =
                (InstantDeserializer)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);
    }

    // Helper method to find Strings of form "all digits" and "digits-comma-digits"
    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)
    {
        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
        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 // since 2.8.3
    {
        public final long value;
        public final ZoneId zoneId;

        private FromIntegerArguments(long value, ZoneId zoneId)
        {
            this.value = value;
            this.zoneId = zoneId;
        }
    }

    public static class FromDecimalArguments // since 2.8.3
    {
        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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy