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

org.eclipse.rdf4j.model.base.AbstractLiteral Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2020 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/

package org.eclipse.rdf4j.model.base;

import static java.lang.Math.abs;
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
import static java.time.temporal.ChronoField.OFFSET_SECONDS;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.HOURS;
import static java.time.temporal.ChronoUnit.MINUTES;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.time.temporal.ChronoUnit.NANOS;
import static java.time.temporal.ChronoUnit.SECONDS;
import static java.time.temporal.ChronoUnit.YEARS;
import static java.util.Objects.requireNonNull;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.DateTimeException;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.SignStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalUnit;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;

/**
 * Base class for {@link Literal}, offering common functionality.
 *
 * @author Alessandro Bollini
 * @since 3.5.0
 */
@SuppressWarnings("UseOfObsoleteDateTimeApi")
public abstract class AbstractLiteral implements Literal {

	private static final long serialVersionUID = -1286527360744086451L;

	static boolean reserved(IRI datatype) {
		return CoreDatatype.RDF.LANGSTRING.getIri().equals(datatype);
	}

	static boolean reserved(CoreDatatype datatype) {
		return CoreDatatype.RDF.LANGSTRING == datatype;
	}

	/**
	 * Converts this literal to a value.
	 *
	 * @param mapper a function mapping from the label of this literal to its value; returns a {@code null} value or
	 *               throws an {@code IllegalArgumentException} if the label of this literal doesn't represent a value
	 *               of the expected type
	 * @param     the expected value type
	 * @return the value returned by {@code mapper}
	 * @throws NullPointerException if {@code mapper} is {@code null}
	 */
	private  V value(Function mapper) {
		return Optional
				.of(getLabel())
				.map(requireNonNull(mapper, "null mapper"))
				.orElseThrow(() -> new IllegalArgumentException("malformed value"));
	}

	@Override
	public String stringValue() {
		return getLabel();
	}

	@Override
	public boolean booleanValue() {
		return value(BooleanLiteral::parseBoolean);
	}

	@Override
	public byte byteValue() {
		return value(Byte::parseByte);
	}

	@Override
	public short shortValue() {
		return value(Short::parseShort);
	}

	@Override
	public int intValue() {
		return value(Integer::parseInt);
	}

	@Override
	public long longValue() {
		return value(Long::parseLong);
	}

	@Override
	public float floatValue() {
		return value(NumberLiteral::parseFloat);
	}

	@Override
	public double doubleValue() {
		return value(NumberLiteral::parseDouble);
	}

	@Override
	public BigInteger integerValue() {
		return value(BigInteger::new);
	}

	@Override
	public BigDecimal decimalValue() {
		return value(BigDecimal::new);
	}

	@Override
	public TemporalAccessor temporalAccessorValue() throws DateTimeException {
		return value(TemporalAccessorLiteral::parseTemporalAccessor);
	}

	@Override
	public TemporalAmount temporalAmountValue() throws DateTimeException {
		return value(TemporalAmountLiteral::parseTemporalAmount);
	}

	@Override
	public XMLGregorianCalendar calendarValue() {
		return value(CalendarLiteral::parseCalendar);
	}

	@Override
	public boolean equals(Object o) {
		return this == o || o instanceof Literal
				&& getLabel().equals(((Literal) o).getLabel())
				&& getDatatype().equals(((Literal) o).getDatatype())
				&& equals(getLanguage(), ((Literal) o).getLanguage());
	}

	@Override
	public int hashCode() {
		return getLabel().hashCode();
	}

	@Override
	public String toString() {

		final String label = '"' + getLabel() + '"';

		return getLanguage()

				.map(language -> label + '@' + language)

				.orElseGet(() -> CoreDatatype.XSD.STRING == getCoreDatatype() ? label
						: label + "^^<" + getDatatype().stringValue() + ">");
	}

	private boolean equals(Optional x, Optional y) {

		final boolean px = x.isPresent();
		final boolean py = y.isPresent();

		return px && py && x.get().equalsIgnoreCase(y.get()) || !px && !py;
	}

	static class TypedLiteral extends AbstractLiteral {

		private static final long serialVersionUID = -19640527584237291L;

		private final String label;
		private final CoreDatatype coreDatatype;
		private final IRI datatype;

		TypedLiteral(String label) {
			this.label = label;
			this.coreDatatype = CoreDatatype.XSD.STRING;
			this.datatype = CoreDatatype.XSD.STRING.getIri();
		}

		TypedLiteral(String label, IRI datatype) {
			this.label = label;
			if (datatype == null) {
				this.datatype = CoreDatatype.XSD.STRING.getIri();
				this.coreDatatype = CoreDatatype.XSD.STRING;
			} else {
				this.datatype = datatype;
				this.coreDatatype = CoreDatatype.from(datatype);
			}
		}

		TypedLiteral(String label, CoreDatatype datatype) {
			this.label = label;
			this.coreDatatype = Objects.requireNonNull(datatype);
			this.datatype = datatype.getIri();
		}

		TypedLiteral(String label, IRI datatype, CoreDatatype coreDatatype) {
			assert datatype != null;
			assert coreDatatype != null;
			assert coreDatatype == CoreDatatype.NONE || datatype == coreDatatype.getIri();

			this.label = label;
			this.datatype = datatype;
			this.coreDatatype = coreDatatype;
		}

		@Override
		public String getLabel() {
			return label;
		}

		@Override
		public Optional getLanguage() {
			return Optional.empty();
		}

		@Override
		public IRI getDatatype() {
			return datatype;
		}

		@Override
		public CoreDatatype getCoreDatatype() {
			return coreDatatype;
		}
	}

	static class TaggedLiteral extends AbstractLiteral {

		private static final long serialVersionUID = -19640527584237291L;

		private final String label;
		private final String language;

		TaggedLiteral(String label, String language) {
			this.label = label;
			this.language = language;
		}

		@Override
		public String getLabel() {
			return label;
		}

		@Override
		public Optional getLanguage() {
			return Optional.of(language);
		}

		@Override
		public IRI getDatatype() {
			return CoreDatatype.RDF.LANGSTRING.getIri();
		}

		@Override
		public CoreDatatype.RDF getCoreDatatype() {
			return CoreDatatype.RDF.LANGSTRING;
		}
	}

	static class BooleanLiteral extends AbstractLiteral {

		private static final long serialVersionUID = -1162147873619834622L;

		static Boolean parseBoolean(String label) {
			return Optional.of(label)

					.map(String::trim)

					.map(normalized -> normalized.equals("true") || normalized.equals("1") ? Boolean.TRUE
							: normalized.equals("false") || normalized.equals("0") ? Boolean.FALSE
									: null
					)

					.orElse(null);
		}

		private final boolean value;

		BooleanLiteral(boolean value) {
			this.value = value;
		}

		@Override
		public String getLabel() {
			return value ? "true" : "false";
		}

		@Override
		public Optional getLanguage() {
			return Optional.empty();
		}

		@Override
		public IRI getDatatype() {
			return CoreDatatype.XSD.BOOLEAN.getIri();
		}

		@Override
		public CoreDatatype.XSD getCoreDatatype() {
			return CoreDatatype.XSD.BOOLEAN;
		}

		@Override
		public boolean booleanValue() {
			return value;
		}

	}

	static class NumberLiteral extends AbstractLiteral {

		private static final long serialVersionUID = -3201912818064851702L;

		private static final String POSITIVE_INFINITY = "INF";
		private static final String NEGATIVE_INFINITY = "-INF";
		private static final String NAN = "NaN";

		static float parseFloat(String label) {
			return label.equals(POSITIVE_INFINITY) ? Float.POSITIVE_INFINITY
					: label.equals(NEGATIVE_INFINITY) ? Float.NEGATIVE_INFINITY
							: label.equals(NAN) ? Float.NaN
									: Float.parseFloat(label);
		}

		static double parseDouble(String label) {
			return label.equals(POSITIVE_INFINITY) ? Double.POSITIVE_INFINITY
					: label.equals(NEGATIVE_INFINITY) ? Double.NEGATIVE_INFINITY
							: label.equals(NAN) ? Double.NaN
									: Double.parseDouble(label);
		}

		private static String toString(float value) {
			return value == Float.POSITIVE_INFINITY ? POSITIVE_INFINITY
					: value == Float.NEGATIVE_INFINITY ? NEGATIVE_INFINITY
							: Float.isNaN(value) ? NAN
									: Float.toString(value);
		}

		private static String toString(double value) {
			return value == Double.POSITIVE_INFINITY ? POSITIVE_INFINITY
					: value == Double.NEGATIVE_INFINITY ? NEGATIVE_INFINITY
							: Double.isNaN(value) ? NAN
									: Double.toString(value);
		}

		protected Number value;

		private final String label;
		private final CoreDatatype.XSD datatype;

		NumberLiteral(byte value) {
			this(value, Byte.toString(value), CoreDatatype.XSD.BYTE);
		}

		NumberLiteral(short value) {
			this(value, Short.toString(value), CoreDatatype.XSD.SHORT);
		}

		NumberLiteral(int value) {
			this(value, Integer.toString(value), CoreDatatype.XSD.INT);
		}

		NumberLiteral(long value) {
			this(value, Long.toString(value), CoreDatatype.XSD.LONG);
		}

		NumberLiteral(float value) {
			this(value, toString(value), CoreDatatype.XSD.FLOAT);
		}

		NumberLiteral(double value) {
			this(value, toString(value), CoreDatatype.XSD.DOUBLE);
		}

		NumberLiteral(Number value, String label, CoreDatatype.XSD datatype) {
			this.value = value;
			this.label = label;
			this.datatype = datatype;
		}

		@Override
		public String getLabel() {
			return label;
		}

		@Override
		public Optional getLanguage() {
			return Optional.empty();
		}

		@Override
		public IRI getDatatype() {
			return datatype.getIri();
		}

		@Override
		public byte byteValue() {
			return value.byteValue();
		}

		@Override
		public short shortValue() {
			return value.shortValue();
		}

		@Override
		public int intValue() {
			return value.intValue();
		}

		@Override
		public long longValue() {
			return value.longValue();
		}

		@Override
		public float floatValue() {
			return value.floatValue();
		}

		@Override
		public double doubleValue() {
			return value.doubleValue();
		}

		@Override
		public CoreDatatype getCoreDatatype() {
			return datatype;
		}
	}

	static class IntegerLiteral extends NumberLiteral {

		private static final long serialVersionUID = -4274941248972496665L;

		IntegerLiteral(BigInteger value) {
			super(value, value.toString(), CoreDatatype.XSD.INTEGER);
		}

		@Override
		public BigInteger integerValue() {
			return (BigInteger) value;
		}

		@Override
		public BigDecimal decimalValue() {
			return new BigDecimal((BigInteger) value);
		}

	}

	static class DecimalLiteral extends NumberLiteral {

		private static final long serialVersionUID = -4382147098035463886L;

		DecimalLiteral(BigDecimal value) {
			super(value, value.toPlainString(), CoreDatatype.XSD.DECIMAL);
		}

		@Override
		public BigInteger integerValue() {
			return ((BigDecimal) value).toBigInteger();
		}

		@Override
		public BigDecimal decimalValue() {
			return (BigDecimal) value;
		}

	}

	static class TemporalAccessorLiteral extends AbstractLiteral {

		private static final long serialVersionUID = -6089251668767105663L;

		private static final ChronoField[] FIELDS = {
				YEAR, MONTH_OF_YEAR, DAY_OF_MONTH,
				HOUR_OF_DAY, MINUTE_OF_HOUR, SECOND_OF_MINUTE, NANO_OF_SECOND,
				OFFSET_SECONDS
		};

		private static final DateTimeFormatter LOCAL_TIME_FORMATTER = new DateTimeFormatterBuilder()

				.appendValue(HOUR_OF_DAY, 2)
				.appendLiteral(':')
				.appendValue(MINUTE_OF_HOUR, 2)
				.appendLiteral(':')
				.appendValue(SECOND_OF_MINUTE, 2)

				.optionalStart()
				.appendFraction(NANO_OF_SECOND, 1, 9, true)

				.toFormatter();

		private static final DateTimeFormatter OFFSET_TIME_FORMATTER = new DateTimeFormatterBuilder()

				.append(LOCAL_TIME_FORMATTER)

				.optionalStart()
				.appendOffsetId()

				.toFormatter();

		private static final DateTimeFormatter LOCAL_DATE_FORMATTER = new DateTimeFormatterBuilder()

				.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)

				.optionalStart()
				.appendLiteral('-')
				.appendValue(MONTH_OF_YEAR, 2)

				.optionalStart()
				.appendLiteral('-')
				.appendValue(DAY_OF_MONTH, 2)

				.toFormatter();

		private static final DateTimeFormatter OFFSET_DATE_FORMATTER = new DateTimeFormatterBuilder()

				.append(LOCAL_DATE_FORMATTER)

				.optionalStart()
				.appendOffsetId()

				.toFormatter();

		private static final DateTimeFormatter DATETIME_FORMATTER = new DateTimeFormatterBuilder()

				.append(LOCAL_DATE_FORMATTER)

				.optionalStart()
				.appendLiteral('T')
				.append(LOCAL_TIME_FORMATTER)
				.optionalEnd()

				.optionalStart()
				.appendOffsetId()

				.toFormatter();

		private static final DateTimeFormatter DASH_FORMATTER = new DateTimeFormatterBuilder()

				.appendLiteral("--")

				.optionalStart()
				.appendValue(MONTH_OF_YEAR, 2)
				.optionalEnd()

				.optionalStart()
				.appendLiteral('-')
				.appendValue(DAY_OF_MONTH, 2)

				.toFormatter();

		private static final Map DATATYPES = datatypes();
		private static final Map FORMATTERS = formatters();

		static TemporalAccessor parseTemporalAccessor(String label) throws DateTimeException {

			TemporalAccessor value = formatter(label).parse(label);

			if (!DATATYPES.containsKey(key(value))) {
				throw new DateTimeException(String.format(
						"label <%s> is not a valid lexical representation of an XML Schema date/time datatype", label
				));
			}

			return value;
		}

		private static Map datatypes() {

			int date = key(YEAR, MONTH_OF_YEAR, DAY_OF_MONTH);
			int time = key(HOUR_OF_DAY, MINUTE_OF_HOUR, SECOND_OF_MINUTE);
			int nano = key(NANO_OF_SECOND);
			int zone = key(OFFSET_SECONDS);

			Map datatypes = new HashMap<>();

			datatypes.put(date + time, CoreDatatype.XSD.DATETIME);
			datatypes.put(date + time + nano, CoreDatatype.XSD.DATETIME);
			datatypes.put((date + time + zone), CoreDatatype.XSD.DATETIME);
			datatypes.put((date + time + nano + zone), CoreDatatype.XSD.DATETIME);

			datatypes.put(time, CoreDatatype.XSD.TIME);
			datatypes.put(time + nano, CoreDatatype.XSD.TIME);
			datatypes.put(time + zone, CoreDatatype.XSD.TIME);
			datatypes.put(time + nano + zone, CoreDatatype.XSD.TIME);

			datatypes.put(date, CoreDatatype.XSD.DATE);
			datatypes.put(date + zone, CoreDatatype.XSD.DATE);

			datatypes.put(key(YEAR, MONTH_OF_YEAR), CoreDatatype.XSD.GYEARMONTH);
			datatypes.put(key(YEAR), CoreDatatype.XSD.GYEAR);
			datatypes.put(key(MONTH_OF_YEAR, DAY_OF_MONTH), CoreDatatype.XSD.GMONTHDAY);
			datatypes.put(key(DAY_OF_MONTH), CoreDatatype.XSD.GDAY);
			datatypes.put(key(MONTH_OF_YEAR), CoreDatatype.XSD.GMONTH);

			return datatypes;
		}

		private static Map formatters() {

			final Map formatters = new EnumMap<>(CoreDatatype.XSD.class);

			formatters.put(CoreDatatype.XSD.DATETIME, DATETIME_FORMATTER);
			formatters.put(CoreDatatype.XSD.TIME, OFFSET_TIME_FORMATTER);
			formatters.put(CoreDatatype.XSD.DATE, OFFSET_DATE_FORMATTER);

			formatters.put(CoreDatatype.XSD.GYEARMONTH, LOCAL_DATE_FORMATTER);
			formatters.put(CoreDatatype.XSD.GYEAR, LOCAL_DATE_FORMATTER);
			formatters.put(CoreDatatype.XSD.GMONTHDAY, DASH_FORMATTER);
			formatters.put(CoreDatatype.XSD.GDAY, DASH_FORMATTER);
			formatters.put(CoreDatatype.XSD.GMONTH, DASH_FORMATTER);

			return formatters;
		}

		private static DateTimeFormatter formatter(String label) {
			if (label.startsWith("--")) {

				return DASH_FORMATTER;

			} else if (label.length() >= 8 && label.charAt(2) == ':') {

				return OFFSET_TIME_FORMATTER;

			} else {

				return DATETIME_FORMATTER;

			}
		}

		private static int key(TemporalAccessor value) {
			return key(value::isSupported, FIELDS);
		}

		private static int key(ChronoField... fields) {
			return key(field -> true, fields);
		}

		private static int key(Predicate include, ChronoField... fields) {

			int index = 0;

			for (ChronoField field : fields) {
				if (include.test(field)) {
					index += 1 << field.ordinal() + 1;
				}
			}

			return index;
		}

		private final TemporalAccessor value;

		private final String label;
		private final CoreDatatype.XSD datatype;

		TemporalAccessorLiteral(TemporalAccessor value) {

			this.value = value;

			datatype = DATATYPES.get(key(value));

			if (datatype == null) {
				throw new IllegalArgumentException(String.format(
						"value <%s> cannot be represented by an XML Schema date/time datatype", value
				));
			}

			this.label = FORMATTERS.get(datatype).format(value);
		}

		@Override
		public String getLabel() {
			return label;
		}

		@Override
		public Optional getLanguage() {
			return Optional.empty();
		}

		@Override
		public IRI getDatatype() {
			return datatype.getIri();
		}

		@Override
		public TemporalAccessor temporalAccessorValue() {
			return value;
		}

		@Override
		public CoreDatatype getCoreDatatype() {
			return datatype;
		}

	}

	static class TemporalAmountLiteral extends AbstractLiteral {

		private static final long serialVersionUID = -447302801371093467L;

		private static final Collection UNITS = EnumSet.of(
				YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS, NANOS
		);

		private static final Pattern PATTERN = Pattern.compile("(?-)?" +
				"P" +
				"(?:(?<" + YEARS + ">\\d+)Y)?" +
				"(?:(?<" + MONTHS + ">\\d+)M)?" +
				"(?:(?<" + DAYS + ">\\d+)D)?" +
				"(?




© 2015 - 2024 Weber Informatics LLC | Privacy Policy