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

world.data.jdbc.model.LiteralFactory Maven / Gradle / Ivy

/*
 * dw-jdbc
 * Copyright 2017 data.world, Inc.

 * 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.
 *
 * This product includes software developed at data.world, Inc.(http://www.data.world/).
 */
package world.data.jdbc.model;

import lombok.experimental.UtilityClass;
import world.data.jdbc.vocab.Xsd;

import javax.annotation.Nullable;
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.Month;
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.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Optional;

/**
 * Factory methods for {@link Literal} objects of various data types.  It's OK to create {@link Literal} objects
 * directly with {@code new Literal(...)} but these methods may be more convenient.
 */
@UtilityClass
@SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"})
public final class LiteralFactory {

    public static final Literal TRUE = new Literal("true", Xsd.BOOLEAN);
    public static final Literal FALSE = new Literal("false", Xsd.BOOLEAN);

    /** Returns a non-localized {@code xsd:string}. */
    public static Literal createString(String string) {
        return new Literal(string, Xsd.STRING);
    }

    /** Returns an {@code xsd:string}, optionally localized. */
    public static Literal createString(String string, @Nullable String lang) {
        return new Literal(string, Xsd.STRING, lang);
    }

    /** Returns an {@code xsd:boolean}. */
    public static Literal createBoolean(boolean value) {
        return value ? TRUE : FALSE;
    }

    /** Returns an {@code xsd:byte}. */
    public static Literal createByte(byte value) {
        return new Literal(Byte.toString(value), Xsd.BYTE);
    }

    /** Returns an {@code xsd:short}. */
    public static Literal createShort(short value) {
        return new Literal(Short.toString(value), Xsd.SHORT);
    }

    /** Returns an {@code xsd:int}.  Note that usually {@link #createInteger(long)} is preferred. */
    public static Literal createInt(int value) {
        return new Literal(Integer.toString(value), Xsd.INT);
    }

    /** Returns an {@code xsd:long}.  Note that usually {@link #createInteger(long)} is preferred. */
    public static Literal createLong(long value) {
        return new Literal(Long.toString(value), Xsd.LONG);
    }

    /** Returns an {@code xsd:integer} (arbitrary-precision integer, the most common integer rdf type). */
    public static Literal createInteger(long value) {
        return new Literal(Long.toString(value), Xsd.INTEGER);
    }

    /** Returns an {@code xsd:integer} (arbitrary-precision integer, the most common integer rdf type). */
    public static Literal createInteger(BigInteger value) {
        return new Literal(value.toString(), Xsd.INTEGER);
    }

    /** Returns an {@code xsd:float}. */
    public static Literal createFloat(float value) {
        return new Literal(!Float.isInfinite(value) ? Float.toString(value) : value > 0 ? "INF" : "-INF", Xsd.FLOAT);
    }

    /** Returns an {@code xsd:double}. */
    public static Literal createDouble(double value) {
        return new Literal(!Double.isInfinite(value) ? Double.toString(value) : value > 0 ? "INF" : "-INF", Xsd.DOUBLE);
    }

    /** Returns an {@code xsd:decimal} (arbitrary-precision decimal, the most common non-integer numeric rdf type). */
    public static Literal createDecimal(double value) {
        return createDecimal(BigDecimal.valueOf(value));
    }

    /** Returns an {@code xsd:decimal} (arbitrary-precision decimal, the most common non-integer numeric rdf type). */
    public static Literal createDecimal(BigDecimal value) {
        return new Literal(value.toPlainString(), Xsd.DECIMAL);
    }

    /** Returns an {@code xsd:yearMonthDuration}. */
    public static Literal createYearMonthDuration(Period value) {
        if (value.getDays() != 0) {
            throw new IllegalArgumentException("Year month duration may not contain days: " + value);
        }
        return new Literal(value.toString(), Xsd.YEARMONTHDURATION);
    }

    /** Returns an {@code xsd:dayTimeDuration}. */
    public static Literal createDayTimeDuration(Duration value) {
        return new Literal(value.toString(), Xsd.DAYTIMEDURATION);
    }

    /** Returns an {@code xsd:gYearMonth}. */
    public static Literal createYearMonth(TemporalAccessor value) {
        return createYearMonth(YearMonth.from(toUtcDateTime(value).orElse(value)));
    }

    /** Returns an {@code xsd:gYearMonth}. */
    public static Literal createYearMonth(YearMonth value) {
        return new Literal(value.toString(), Xsd.GYEARMONTH);
    }

    /** Returns an {@code xsd:gMonthDay}. */
    public static Literal createMonthDay(TemporalAccessor value) {
        return createMonthDay(MonthDay.from(toUtcDateTime(value).orElse(value)));
    }

    /** Returns an {@code xsd:gMonthDay}. */
    public static Literal createMonthDay(MonthDay value) {
        return new Literal(value.toString(), Xsd.GMONTHDAY);
    }

    /** Returns an {@code xsd:gYear}. */
    public static Literal createYear(TemporalAccessor value) {
        return createYear(Year.from(toUtcDateTime(value).orElse(value)));
    }

    /** Returns an {@code xsd:gYear}. */
    public static Literal createYear(Year value) {
        return new Literal(value.toString(), Xsd.GYEAR);
    }

    /** Returns an {@code xsd:gMonth}. */
    public static Literal createMonth(TemporalAccessor value) {
        return createMonth(Month.from(toUtcDateTime(value).orElse(value)));
    }

    /** Returns an {@code xsd:gMonth}. */
    public static Literal createMonth(Month value) {
        return new Literal("--" + value.getValue(), Xsd.GMONTH);
    }

    /** Returns an {@code xsd:gDay}. */
    public static Literal createDay(TemporalAccessor value) {
        return createDay(toUtcDateTime(value).orElse(value).get(ChronoField.DAY_OF_MONTH));
    }

    /** Returns an {@code xsd:gDay}. */
    public static Literal createDay(int dayOfMonth) {
        ChronoField.DAY_OF_MONTH.checkValidIntValue(dayOfMonth);
        return new Literal("---" + dayOfMonth, Xsd.GDAY);
    }

    /** Returns an {@code xsd:date}. */
    public static Literal createDate(TemporalAccessor value) {
        return createDate(LocalDate.from(toUtcDateTime(value).orElse(value)));
    }

    /** Returns an {@code xsd:date}. */
    public static Literal createDate(LocalDate value) {
        return new Literal(DateTimeFormatter.ISO_LOCAL_DATE.format(value), Xsd.DATE);
    }

    /** Returns an {@code xsd:dateTime}.  It will have a 'Z' time zone suffix if the temporal value has sufficient info. */
    public static Literal createDateTime(TemporalAccessor value) {
        return toUtcDateTime(value).map(t -> LiteralFactory.createDateTime(OffsetDateTime.from(t)))
                .orElseGet(() -> LiteralFactory.createDateTime(LocalDateTime.from(value)));
    }

    /** Returns an {@code xsd:dateTime} with a 'Z' suffix. */
    public static Literal createDateTime(OffsetDateTime value) {
        value = value.withOffsetSameInstant(ZoneOffset.UTC);
        return new Literal(trimZeros(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value)), Xsd.DATETIME);
    }

    /** Returns an {@code xsd:dateTime}. */
    public static Literal createDateTime(LocalDateTime value) {
        return new Literal(trimZeros(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(value)), Xsd.DATETIME);
    }

    /** Returns an {@code xsd:time}.  It will have a 'Z' time zone suffix if the temporal value has sufficient info. */
    public static Literal createTime(TemporalAccessor value) {
        return toUtcTime(value).map(t -> LiteralFactory.createTime(OffsetTime.from(t)))
                .orElseGet(() -> LiteralFactory.createTime(LocalTime.from(value)));
    }

    /** Returns an {@code xsd:time} with a 'Z' suffix. */
    public static Literal createTime(OffsetTime value) {
        value = value.withOffsetSameInstant(ZoneOffset.UTC);
        return new Literal(trimZeros(DateTimeFormatter.ISO_OFFSET_TIME.format(value)), Xsd.TIME);
    }

    /** Returns an {@code xsd:time}. */
    public static Literal createTime(LocalTime value) {
        return new Literal(trimZeros(DateTimeFormatter.ISO_LOCAL_TIME.format(value)), Xsd.TIME);
    }

    //
    // The java.time.temporal.TemporalAccessor-related code is tricky to write and understand.  To help, here's
    // a table of temporal types and the ChronoFields they support, for relevant/interesting ChronoFields:
    //
    //             Essential To:           Instant             |   Local/Offset+Time/Date/DateTime    |      Year+Month+Day
    // Class              Field: InstantSeconds  NanoOfSecond  |  EpochDay  NanoOfDay  OffsetSeconds  |  Year  MonthOfYear  DayOfMonth
    // java.time.YearMonth        .               .            |   .         .          .             |   X     X            .
    // java.time.MonthDay         .               .            |   .         .          .             |   .     X            X
    // java.time.LocalTime        .               X            |   .         X          .             |   .     .            .
    // java.time.OffsetTime       .               X            |   .         X          X             |   .     .            .
    // java.time.LocalDate        .               .            |   X         .          .             |   X     X            X
    // java.time.LocalDateTime    .               X            |   X         X          .             |   X     X            X
    // java.time.OffsetDateTime   X               X            |   X         X          X             |   X     X            X
    // java.time.ZonedDateTime    X               X            |   X         X          X             |   X     X            X
    // java.time.Instant          X               X            |   .         .          .             |   .     .            .
    //

    /** Converts the value to an {@link OffsetDateTime} in the UTC time zone, if possible. */
    private Optional toUtcDateTime(TemporalAccessor value) {
        if (value.isSupported(ChronoField.OFFSET_SECONDS)) {
            // Has time zone so normalize to utc, eg. OffsetDateTime, ZonedDateTime
            return Optional.of(OffsetDateTime.from(value).withOffsetSameInstant(ZoneOffset.UTC));
        } else if (value.isSupported(ChronoField.INSTANT_SECONDS)) {
            // Assume utc, eg. Instant
            return Optional.of(Instant.from(value).atOffset(ZoneOffset.UTC));
        }
        return Optional.empty();
    }

    /** Converts the value to an {@link OffsetTime} in the UTC time zone, if possible. */
    private Optional toUtcTime(TemporalAccessor value) {
        if (value.isSupported(ChronoField.OFFSET_SECONDS)) {
            // Has time zone so normalize to utc, eg. OffsetTime, OffsetDateTime, ZonedDateTime
            return Optional.of(OffsetTime.from(value).withOffsetSameInstant(ZoneOffset.UTC));
        } else if (value.isSupported(ChronoField.INSTANT_SECONDS)) {
            // Assume utc, Eg. Instant
            return Optional.of(Instant.from(value).atOffset(ZoneOffset.UTC).toOffsetTime());
        }
        return Optional.empty();
    }

    /**
     * Returns a {@code xsd:date} without a time zone suffix.
     *
     * @deprecated The {@link #createDate(LocalDate)} method is preferred.
     */
    @Deprecated
    public static Literal createDate(java.util.Date value) {
        return createDate(value.toInstant());
    }

    /**
     * Returns a {@code xsd:date} without a time zone suffix.
     *
     * @deprecated The {@link #createDate(LocalDate)} method is preferred.
     */
    @Deprecated
    public static Literal createDate(java.sql.Date value) {
        return createDate(value.toLocalDate());
    }

    /**
     * Returns a {@code xsd:date} without a time zone suffix.
     *
     * @deprecated The {@link #createDate(LocalDate)} method is preferred.
     */
    @Deprecated
    public static Literal createDate(java.sql.Date value, Calendar calendar) {
        return createDate(toInstantUsingCalendar(value, calendar));
    }

    /** Returns a {@code xsd:dateTime} with a UTC time zone suffix. */
    public static Literal createDateTime(java.util.Date value) {
        return createDateTime(value.toInstant());
    }

    /**
     * Returns a {@code xsd:dateTime} without a time zone suffix.
     *
     * @deprecated The {@link #createDateTime(LocalDateTime)} method is preferred.
     */
    @Deprecated
    public static Literal createDateTime(java.sql.Timestamp value) {
        return createDateTime(value.toLocalDateTime());
    }

    /**
     * Returns a {@code xsd:dateTime} with a UTC time zone suffix.
     *
     * @deprecated The {@link #createDateTime(OffsetDateTime)} method is preferred.
     */
    @Deprecated
    public static Literal createDateTime(java.sql.Timestamp value, Calendar calendar) {
        return createDateTime(toInstantUsingCalendar(value, calendar));
    }

    /**
     * Returns a {@code xsd:time} without a time zone suffix.
     *
     * @deprecated The {@link #createTime(LocalTime)} method is preferred.
     */
    @Deprecated
    public static Literal createTime(java.sql.Time value) {
        return createTime(value.toLocalTime().withNano((int) (value.getTime() % 1000) * 1_000_000));
    }

    /**
     * Returns a {@code xsd:time} with a UTC time zone suffix.
     *
     * @deprecated The {@link #createTime(OffsetTime)} method is preferred.
     */
    @Deprecated
    public static Literal createTime(java.sql.Time value, Calendar calendar) {
        return createTime(toInstantUsingCalendar(value, calendar));
    }

    private Instant toInstantUsingCalendar(java.util.Date value, Calendar calendar) {
        calendar.setTime(value);
        return calendar.toInstant();
    }

    /** Trims trailing zeros in the fractional component of a time string. */
    private static String trimZeros(String string) {
        boolean utc = string.endsWith("Z");
        int end = string.length() - 1 - (utc ? 1 : 0);
        // Find the location of the last 'significant' digit, the last non-zero
        int sig = end;
        for (; sig >= 0; sig--) {
            if (string.charAt(sig) != '0') {
                break;
            }
        }
        if (sig == end) {
            return string;  // Nothing to trim
        }
        // Find the location of the decimal separator, the last '.' character
        int dec = sig;
        for (; dec >= 0; dec--) {
            char ch = string.charAt(dec);
            if (ch == '.') {
                // Trim trailing zeros between 'sig' and 'end', possibly removing the decimal separator as well
                int stop = (dec == sig) ? sig : (sig + 1);
                StringBuilder buf = new StringBuilder(stop + (utc ? 1 : 0));
                buf.append(string, 0, stop);
                if (utc) {
                    buf.append('Z');
                }
                return buf.toString();
            } else if (ch < '0' || ch > '9') {
                break;
            }
        }
        return string;  // Nothing to trim
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy