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

com.metreeca.json.Values Maven / Gradle / Ivy

/*
 * Copyright © 2013-2021 Metreeca srl
 *
 * 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 com.metreeca.json;

import org.eclipse.rdf4j.model.*;
import org.eclipse.rdf4j.model.base.AbstractNamespace;
import org.eclipse.rdf4j.model.base.AbstractValueFactory;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.XSD;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Locale.ROOT;
import static java.util.UUID.nameUUIDFromBytes;
import static java.util.UUID.randomUUID;
import static java.util.stream.Collectors.joining;


/**
 * Value utilities.
 */
public final class Values {

	private static final String IRIScheme="(?(?[-+\\w]+):)";
	private static final String IRIHost="(?//(?[^/?#]*))";
	private static final String IRIQuery="(?\\?(?[^#]*))";
	private static final String IRIFragment="(?#(?.*))";
	private static final String IRIPath="(?(?[^?#]*)"+IRIQuery+"?"+IRIFragment+"?)";


	/**
	 * A pattern matching absolute IRIs.
	 */
	public static final Pattern AbsoluteIRIPattern=Pattern.compile("^"+IRIScheme+IRIHost+"?"+IRIPath+"$");

	/**
	 * A pattern matching IRI components.
	 *
	 * @see RFC 3986 Uniform Resource Identifier (URI): Generic
	 * Syntax - Appendix B.  Parsing a URI Reference with a Regular Expression
	 */
	public static final Pattern IRIPattern=Pattern.compile("^"+IRIScheme+"?"+IRIHost+"?"+IRIPath+"$");


	private static final Pattern NewlinePattern=Pattern.compile("\n");


	private static final ValueFactory factory=new AbstractValueFactory() {}; // before constant initialization
	private static final Comparator comparator=new ValueComparator();

	private static final ThreadLocal exponential=ThreadLocal.withInitial(() ->
			new DecimalFormat("0.0#########E0", DecimalFormatSymbols.getInstance(ROOT)) // ;( not thread-safe
	);

	private static final char[] HexDigits="0123456789abcdef".toCharArray();


	//// Constants /////////////////////////////////////////////////////////////////////////////////////////////////////

	public static final Literal True=literal(true);
	public static final Literal False=literal(false);


	//// Internal Namespace ////////////////////////////////////////////////////////////////////////////////////////////

	public static final String Base="app:/";

	public static final IRI Root=iri(Base);

	public static final Namespace NS=namespace("", Base+"terms#");


	public static IRI term(final String name) {
		return name == null ? null : iri(NS.getName(), name);
	}

	public static IRI item(final String name) {
		return name == null ? null : iri(Root, name);
	}


	//// Extended Datatypes ////////////////////////////////////////////////////////////////////////////////////////////

	public static final IRI ValueType=term("value"); // abstract datatype IRI for values
	public static final IRI ResourceType=term("resource"); // abstract datatype IRI for resources
	public static final IRI BNodeType=term("bnode"); // datatype IRI for blank nodes
	public static final IRI IRIType=term("iri"); // datatype IRI for IRI references
	public static final IRI LiteralType=term("literal"); // abstract datatype IRI for literals


	public static boolean derives(final IRI upper, final IRI lower) {
		return upper != null && lower != null && (
				upper.equals(ValueType)
						|| upper.equals(ResourceType) && resource(lower)
						|| upper.equals(LiteralType) && literal(lower)
		);
	}


	private static boolean resource(final IRI type) {
		return type.equals(ResourceType) || type.equals(BNodeType) || type.equals(IRIType);
	}

	private static boolean literal(final IRI type) {
		return type.equals(LiteralType) || !type.equals(ValueType) && !resource(type);
	}


	//// Inverse Predicates ////////////////////////////////////////////////////////////////////////////////////////////

	/**
	 * An IRI scheme for inverse predicates ({@value}).
	 */
	private static final String InverseScheme="inverse:";


	/**
	 * Checks predicate direction.
	 *
	 * @param predicate the IRI identifying the predicate
	 *
	 * @return {@code true} if {@code predicate} identifies a direct predicate; {@code false} if {@code predicate}
	 * identifies an {@link #inverse(IRI) inverse} predicate
	 *
	 * @throws NullPointerException if {@code predicate} is null
	 */
	public static boolean direct(final IRI predicate) {

		if ( predicate == null ) {
			throw new NullPointerException("null predicate");
		}

		return !predicate.stringValue().startsWith(InverseScheme);
	}

	/**
	 * Creates an inverse predicate.
	 *
	 * @param predicate the IRI identifying the predicate
	 *
	 * @return the inverse version of {@code predicate}
	 *
	 * @throws NullPointerException if {@code predicate} is null
	 */
	public static IRI inverse(final IRI predicate) {

		if ( predicate == null ) {
			throw new NullPointerException("null predicate");
		}

		final String label=predicate.stringValue();

		return label.startsWith(InverseScheme)
				? iri(label.substring(InverseScheme.length()))
				: iri(InverseScheme+label);
	}

	/**
	 * Traverses a predicate.
	 *
	 * @param predicate the IRI identifying the predicate to be traversed
	 * @param direct    a predicate mapper to be executed if {@code predicate} is {@link #direct(IRI) direct}
	 * @param inverse   a predicate mapper to be executed if {@code predicate} is {@link #inverse(IRI) inverse}
	 * @param        the type of the value returned by predicate mappers
	 *
	 * @return the value returned by the predicate mapper selected according to the direction of {@code predicate}
	 *
	 * @throws NullPointerException if any argument is null
	 */
	public static  V traverse(final IRI predicate, final Function direct, final Function inverse) {

		if ( predicate == null ) {
			throw new NullPointerException("null predicate");
		}

		if ( direct == null ) {
			throw new NullPointerException("null direct");
		}

		if ( inverse == null ) {
			throw new NullPointerException("null inverse");
		}

		return predicate.stringValue().startsWith(InverseScheme)
				? inverse.apply(iri(predicate.stringValue().substring(InverseScheme.length())))
				: direct.apply(predicate);
	}


	//// Comparator ////////////////////////////////////////////////////////////////////////////////////////////////////

	public static int compare(final Value x, final Value y) {
		return comparator.compare(x, y);
	}


	//// Accessors /////////////////////////////////////////////////////////////////////////////////////////////////////

	public static boolean is(final Value value, final IRI datatype) {
		return value != null && (type(value).equals(datatype)
				|| value instanceof Resource && ResourceType.equals(datatype)
				|| value instanceof Literal && LiteralType.equals(datatype)
				|| ValueType.equals(datatype)
		);
	}


	public static String text(final Value value) {
		return value == null ? null : value.stringValue();
	}

	public static IRI type(final Value value) {
		return value == null ? null
				: value instanceof BNode ? BNodeType
				: value instanceof IRI ? IRIType
				: value instanceof Literal ? ((Literal)value).getDatatype()
				: null; // unexpected
	}

	public static String lang(final Value value) {
		return value instanceof Literal ? ((Literal)value).getLanguage().orElse(null) : null;
	}


	//// Factories /////////////////////////////////////////////////////////////////////////////////////////////////////

	public static ValueFactory factory() {
		return factory;
	}


	public static Namespace namespace(final String prefix, final String name) {
		return prefix == null || name == null ? null : new AbstractNamespace() {

			@Override public String getPrefix() { return prefix; }

			@Override public String getName() { return name; }

		};
	}


	public static Predicate pattern(
			final Value subject, final Value predicate, final Value object
	) {
		return statement
				-> (subject == null || subject.equals(statement.getSubject()))
				&& (predicate == null || predicate.equals(statement.getPredicate()))
				&& (object == null || object.equals(statement.getObject()));
	}


	public static Statement statement(
			final Resource subject, final IRI predicate, final Value object
	) {
		return subject == null || predicate == null || object == null ? null
				: factory.createStatement(subject, predicate, object);
	}

	public static Statement statement(
			final Resource subject, final IRI predicate, final Value object, final Resource context
	) {
		return subject == null || predicate == null || object == null ? null
				: factory.createStatement(subject, predicate, object, context);
	}


	public static Triple triple(
			final Resource subject, final IRI predicate, final Value object
	) {
		return subject == null || predicate == null || object == null ? null
				: factory.createTriple(subject, predicate, object);
	}


	public static Value value(final Object object) {
		return object == null ? null

				: object instanceof Value ? (Value)object

				: object instanceof URI ? iri((URI)object)
				: object instanceof URL ? iri((URL)object)

				: literal(object);
	}

	private static  V value(final Class type) {
		throw new IllegalArgumentException(String.format("unsupported object type <%s>", type.getName()));
	}


	public static BNode bnode() {
		return factory.createBNode();
	}

	public static BNode bnode(final String id) {

		if ( id == null ) {
			throw new NullPointerException("null id");
		}

		return factory.createBNode(id.startsWith("_:") ? id.substring(2) : id);
	}


	public static IRI iri() {
		return factory.createIRI("urn:uuid:", uuid());
	}

	public static IRI iri(final Object object) {
		return object == null ? null

				: object instanceof IRI ? (IRI)object

				: object instanceof URI ? iri((URI)object)
				: object instanceof URL ? iri((URL)object)

				: value(object.getClass());
	}

	public static IRI iri(final URI uri) {
		return uri == null ? null : factory.createIRI(uri.toString());
	}

	public static IRI iri(final URL url) {
		return url == null ? null : factory.createIRI(url.toString());
	}

	public static IRI iri(final String iri) {
		return iri == null ? null : factory.createIRI(iri);
	}

	public static IRI iri(final IRI space, final String name) {
		return space == null || name == null ? null : iri(space.stringValue(), name);
	}

	public static IRI iri(final String space, final String name) {
		return space == null || name == null ? null
				: factory.createIRI(space, space.endsWith("/") && name.startsWith("/") ? name.substring(1) : name);
	}


	public static Literal literal(final Object object) {
		return object == null ? null

				: object instanceof Literal ? (Literal)object

				: object instanceof Boolean ? literal(((Boolean)object).booleanValue())

				: object instanceof Byte ? literal(((Byte)object).byteValue())
				: object instanceof Short ? literal(((Short)object).shortValue())
				: object instanceof Integer ? literal(((Integer)object).intValue())
				: object instanceof Long ? literal(((Long)object).longValue())
				: object instanceof Float ? literal(((Float)object).floatValue())
				: object instanceof Double ? literal(((Double)object).doubleValue())
				: object instanceof BigInteger ? literal((BigInteger)object)
				: object instanceof BigDecimal ? literal((BigDecimal)object)

				: object instanceof TemporalAccessor ? literal((TemporalAccessor)object)
				: object instanceof TemporalAmount ? literal((TemporalAmount)object)

				: object instanceof byte[] ? literal((byte[])object)

				: value(object.getClass());
	}

	public static Literal literal(final boolean value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final byte value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final short value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final int value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final long value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final float value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final double value) {
		return factory.createLiteral(value);
	}

	public static Literal literal(final BigInteger value) {
		return value == null ? null : factory.createLiteral(value);
	}

	public static Literal literal(final BigDecimal value) {
		return value == null ? null : factory.createLiteral(value);
	}

	public static Literal literal(final String value) {
		return value == null ? null : factory.createLiteral(value);
	}


	public static Literal literal(final TemporalAccessor accessor) {
		return accessor == null ? null : factory.createLiteral(accessor);
	}

	public static Literal literal(final TemporalAmount amount) {
		return amount == null ? null : factory.createLiteral(amount);
	}


	public static Literal literal(final byte[] value) {
		return value == null ? null : factory.createLiteral(
				"data:application/octet-stream;base64,"+Base64.getEncoder().encodeToString(value), XSD.ANYURI);
	}


	public static Literal literal(final String value, final String lang) {
		return value == null || lang == null ? null : factory.createLiteral(value, lang);
	}

	public static Literal literal(final String value, final IRI datatype) {
		return value == null || datatype == null ? null : factory.createLiteral(value, datatype);
	}


	///// Converters //////////////////////////////////////////////////////////////////////////////////////////////////

	public static Optional iri(final Value value) {
		return Optional.ofNullable(value).filter(IRI.class::isInstance).map(IRI.class::cast);
	}

	public static Optional literal(final Value value) {
		return Optional.ofNullable(value).filter(Literal.class::isInstance).map(Literal.class::cast);
	}


	public static Optional _boolean(final Value value) {
		return literal(value).map(Literal::booleanValue);
	}


	public static Optional _int(final Value value) {
		return literal(value).map(guard(Literal::intValue));
	}

	public static Optional _long(final Value value) {
		return literal(value).map(guard(Literal::longValue));
	}

	public static Optional __float(final Value value) {
		return literal(value).map(guard(Literal::floatValue));
	}

	public static Optional _double(final Value value) {
		return literal(value).map(guard(Literal::doubleValue));
	}

	public static Optional integer(final Value value) {
		return literal(value).map(guard(Literal::integerValue));
	}

	public static Optional decimal(final Value value) {
		return literal(value).map(guard(Literal::decimalValue));
	}


	public static Optional temporalAccessor(final Value value) {
		return literal(value).map(guard(Literal::temporalAccessorValue));
	}

	public static Optional temporalAmount(final Value value) {
		return literal(value).map(guard(Literal::temporalAmountValue));
	}


	public static Optional string(final Value value) {
		return literal(value).map(Literal::stringValue);
	}


	private static  Function guard(final Function mapper) {
		return v -> {

			try { return mapper.apply(v); } catch ( final RuntimeException e ) { return null; }

		};
	}


	//// Formatters ///////////////////////////////////////////////////////////////////////////////////////////////////

	public static String format(final Statement statement) {
		return statement == null ? null : String.format("%s %s %s",
				format(statement.getSubject()), format(statement.getPredicate()), format(statement.getObject())
		);
	}


	public static String format(final List path) {
		return path == null ? null : path.stream().map(Values::format).collect(joining("/"));
	}

	public static String format(final Collection values) {
		return values == null ? null : values.stream().map(Values::format).collect(joining(", "));
	}


	public static String format(final Value value) {
		return value == null ? null
				: value instanceof BNode ? format((BNode)value)
				: value instanceof IRI ? format((IRI)value)
				: value instanceof Focus ? format((Focus)value)
				: format((Literal)value);
	}

	public static String format(final BNode bnode) {
		return bnode == null ? null : "_:"+bnode.getID();
	}

	public static String format(final IRI iri) { // !!! relativize wrt to base
		return iri == null ? null : traverse(iri,
				direct -> direct.equals(RDF.TYPE) ? "a" : '<'+iri.stringValue()+'>',
				inverse -> "^<"+inverse.stringValue()+'>'
		);
	}

	public static String format(final Focus focus) {
		return focus == null ? null : "{"+focus.stringValue()+"}";
	}

	public static String format(final Literal literal) {
		if ( literal == null ) { return null; } else {

			final IRI type=literal.getDatatype();

			try {

				return type.equals(XSD.BOOLEAN) ? String.valueOf(literal.booleanValue())
						: type.equals(XSD.INTEGER) ? String.valueOf(literal.integerValue())
						: type.equals(XSD.DECIMAL) ? literal.decimalValue().toPlainString()
						: type.equals(XSD.DOUBLE) ? exponential.get().format(literal.doubleValue())
						: type.equals(XSD.STRING) ? quote(literal.getLabel())

						: literal.getLanguage()
						.map(lang -> format(literal.getLabel(), lang))
						.orElseGet(() -> format(literal.getLabel(), type));

			} catch ( final IllegalArgumentException ignored ) {

				return format(literal.getLabel(), type);

			}
		}
	}


	private static String format(final CharSequence label, final String lang) {
		return quote(label)+'@'+lang;
	}

	private static String format(final CharSequence label, final IRI type) {
		return quote(label)+"^^"+format(type);
	}


	//// Helpers ///////////////////////////////////////////////////////////////////////////////////////////////////////

	public static String uuid() {
		return randomUUID().toString();
	}

	public static String uuid(final String text) {
		return text == null ? null : uuid(text.getBytes(UTF_8));
	}

	public static String uuid(final byte[] data) {
		return data == null ? null : nameUUIDFromBytes(data).toString();
	}


	public static String md5() {

		final byte[] bytes=new byte[16];

		ThreadLocalRandom.current().nextBytes(bytes);

		return hex(bytes);
	}

	public static String md5(final String text) {
		return text == null ? null : md5(text.getBytes(UTF_8));
	}

	public static String md5(final byte[] data) {
		try {

			return data == null ? null : hex(MessageDigest.getInstance("MD5").digest(data));

		} catch ( final NoSuchAlgorithmException unexpected ) {
			throw new InternalError(unexpected);
		}
	}


	public static BigInteger integer(final long value) {
		return BigInteger.valueOf(value);
	}

	public static BigInteger integer(final Number value) {
		return value == null ? null
				: value instanceof BigInteger ? (BigInteger)value
				: value instanceof BigDecimal ? ((BigDecimal)value).toBigInteger()
				: BigInteger.valueOf(value.longValue());
	}


	public static BigDecimal decimal(final double value) {
		return BigDecimal.valueOf(value);
	}

	public static BigDecimal decimal(final Number value) {
		return value == null ? null
				: value instanceof BigInteger ? new BigDecimal((BigInteger)value)
				: value instanceof BigDecimal ? (BigDecimal)value
				: BigDecimal.valueOf(value.doubleValue());
	}


	public static String hex(final byte[] bytes) {
		if ( bytes == null ) { return null; } else {

			final char[] hex=new char[bytes.length*2];

			for (int i=0, l=bytes.length; i < l; ++i) {

				final int b=bytes[i]&0xFF;

				hex[2*i]=HexDigits[b >>> 4];
				hex[2*i+1]=HexDigits[b&0x0F];
			}

			return new String(hex);
		}
	}


	public static String quote(final CharSequence text) {

		if ( text == null ) {
			throw new NullPointerException("null text");
		}

		final StringBuilder builder=new StringBuilder(text.length()+text.length()/10);

		builder.append('\''); // use single quote to interoperate with Java/JSON string

		for (int i=0, n=text.length(); i < n; ++i) {
			switch ( text.charAt(i) ) {
				case '\\':
					builder.append("\\\\");
					break;
				case '\'':
					builder.append("\\'");
					break;
				case '\r':
					builder.append("\\r");
					break;
				case '\n':
					builder.append("\\n");
					break;
				case '\t':
					builder.append("\\t");
					break;
				default:
					builder.append(text.charAt(i));
					break;
			}
		}

		builder.append('\'');

		return builder.toString();
	}

	public static String indent(final CharSequence text) {

		if ( text == null ) {
			throw new NullPointerException("null text");
		}

		return NewlinePattern.matcher(text).replaceAll("\n\t");
	}


	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	private Values() {}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy