com.metreeca.json.Values Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metreeca-json Show documentation
Show all versions of metreeca-json Show documentation
A shape-based JSON modelling framework.
The newest version!
/*
* Copyright © 2013-2022 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.Matcher;
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();
private static final int NameLengthLimit=80; // log args length limit
//// 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 IRI Terms=iri(Base, "/terms/");
public static final Namespace NS=namespace("", Terms.stringValue());
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 root(final IRI resource) {
return Optional.ofNullable(resource)
.map(Value::stringValue)
.map(IRIPattern::matcher)
.filter(Matcher::matches)
.map(matcher -> Optional.ofNullable(matcher.group("schemeall")).orElse("")
+Optional.ofNullable(matcher.group("hostall")).orElse("")
+"/"
)
.orElse(Base);
}
public static String path(final IRI resource) {
return Optional.ofNullable(resource)
.map(Value::stringValue)
.map(IRIPattern::matcher)
.filter(Matcher::matches)
.map(matcher -> matcher.group("pathall"))
.orElse("/");
}
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()
: ValueType;
}
public static String lang(final Value value) {
return value instanceof Literal ? ((Literal)value).getLanguage().orElse("") : "";
}
//// Identifiers ///////////////////////////////////////////////////////////////////////////////////////////////////
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 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);
}
}
//// 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 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 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 boolean value) {
return factory.createLiteral(value);
}
public static Literal literal(final Number value) {
return literal(value, false);
}
public static Literal literal(final Number value, final boolean strict) {
return value == null ? null
: value instanceof Byte ? literal(value.byteValue(), strict)
: value instanceof Short ? literal(value.shortValue(), strict)
: value instanceof Integer ? literal(value.intValue(), strict)
: value instanceof Long ? literal(value.longValue(), strict)
: value instanceof Float ? literal(value.floatValue(), strict)
: value instanceof Double ? literal(value.doubleValue(), strict)
: value instanceof BigInteger ? literal((BigInteger)value)
: value instanceof BigDecimal ? literal((BigDecimal)value)
: null;
}
public static Literal literal(final byte value) {
return literal(value, false);
}
public static Literal literal(final byte value, final boolean strict) {
return strict ? factory.createLiteral(value) : factory.createLiteral(BigInteger.valueOf(value));
}
public static Literal literal(final short value) {
return literal(value, false);
}
public static Literal literal(final short value, final boolean strict) {
return strict ? factory.createLiteral(value) : factory.createLiteral(BigInteger.valueOf(value));
}
public static Literal literal(final int value) {
return literal(value, false);
}
public static Literal literal(final int value, final boolean strict) {
return strict ? factory.createLiteral(value) : factory.createLiteral(BigInteger.valueOf(value));
}
public static Literal literal(final long value) {
return literal(value, false);
}
public static Literal literal(final long value, final boolean strict) {
return strict ? factory.createLiteral(value) : factory.createLiteral(BigInteger.valueOf(value));
}
public static Literal literal(final float value) {
return literal(value, false);
}
public static Literal literal(final float value, final boolean strict) {
return strict || Float.isInfinite(value) || Float.isNaN(value) ?
factory.createLiteral(value) : factory.createLiteral(BigDecimal.valueOf(value));
}
public static Literal literal(final double value) {
return literal(value, false);
}
public static Literal literal(final double value, final boolean strict) {
return strict || Double.isInfinite(value) || Double.isNaN(value) ?
factory.createLiteral(value) : factory.createLiteral(BigDecimal.valueOf(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 resource(final Value value) {
return Optional.ofNullable(value).filter(Value::isResource).map(Resource.class::cast);
}
public static Optional bnode(final Value value) {
return Optional.ofNullable(value).filter(Value::isBNode).map(BNode.class::cast);
}
public static Optional iri(final Value value) {
return Optional.ofNullable(value).filter(Value::isIRI).map(IRI.class::cast);
}
public static Optional literal(final Value value) {
return Optional.ofNullable(value).filter(Value::isLiteral).map(Literal.class::cast);
}
public static Optional bool(final Value value) {
return literal(value).map(guard(Literal::booleanValue));
}
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 string(final Value value) {
return literal(value).map(guard(Literal::stringValue));
}
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 Function guard(final Function mapper) {
if ( mapper == null ) {
throw new NullPointerException("null mapper");
}
return value -> {
try {return mapper.apply(value);} 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 extends Value> 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 Focus ? format((Focus)value)
: value instanceof BNode ? format((BNode)value)
: value instanceof IRI ? format((IRI)value)
: format((Literal)value);
}
public static String format(final Frame frame) {
return frame == null ? null : frame.toString();
}
public static String format(final Focus focus) {
return focus == null ? null : "{"+focus.stringValue()+"}";
}
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 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 -> quote(literal.getLabel())+'@'+lang)
.orElseGet(() -> quote(literal.getLabel())+"^^"+format(type));
} catch ( final IllegalArgumentException ignored ) {
return quote(literal.getLabel())+"^^"+format(type);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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");
}
/**
* Clips a string.
*
* @param string the string to be clipped
*
* @return the input {@code string} clipped to a maximum length limit, or {@code null} if {@code string} is null
*/
public static String clip(final String string) {
return string == null || string.isEmpty() ? "?"
: string.indexOf('\n') >= 0 ? clip(string.substring(0, string.indexOf('\n')))
: string.length() > NameLengthLimit ?
string.substring(0, NameLengthLimit/2)+" … "+string.substring(string.length()-NameLengthLimit/2)
: string;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Values() {}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy