Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.jsonldjava.core.JsonLdUtils Maven / Gradle / Ivy
package com.github.jsonldjava.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.github.jsonldjava.utils.JsonLdUrl;
import com.github.jsonldjava.utils.Obj;
public class JsonLdUtils {
private static final int MAX_CONTEXT_URLS = 10;
/**
* Returns whether or not the given value is a keyword (or a keyword alias).
*
* @param v
* the value to check.
* @param [ctx] the active context to check against.
*
* @return true if the value is a keyword, false if not.
*/
static boolean isKeyword(Object key) {
if (!isString(key)) {
return false;
}
return "@base".equals(key) || "@context".equals(key) || "@container".equals(key)
|| "@default".equals(key) || "@embed".equals(key) || "@explicit".equals(key)
|| "@graph".equals(key) || "@id".equals(key) || "@index".equals(key)
|| "@language".equals(key) || "@list".equals(key) || "@omitDefault".equals(key)
|| "@reverse".equals(key) || "@preserve".equals(key) || "@set".equals(key)
|| "@type".equals(key) || "@value".equals(key) || "@vocab".equals(key);
}
public static Boolean deepCompare(Object v1, Object v2, Boolean listOrderMatters) {
if (v1 == null) {
return v2 == null;
} else if (v2 == null) {
return v1 == null;
} else if (v1 instanceof Map && v2 instanceof Map) {
final Map m1 = (Map) v1;
final Map m2 = (Map) v2;
if (m1.size() != m2.size()) {
return false;
}
for (final String key : m1.keySet()) {
if (!m2.containsKey(key)
|| !deepCompare(m1.get(key), m2.get(key), listOrderMatters)) {
return false;
}
}
return true;
} else if (v1 instanceof List && v2 instanceof List) {
final List l1 = (List) v1;
final List l2 = (List) v2;
if (l1.size() != l2.size()) {
return false;
}
// used to mark members of l2 that we have already matched to avoid
// matching the same item twice for lists that have duplicates
final boolean alreadyMatched[] = new boolean[l2.size()];
for (int i = 0; i < l1.size(); i++) {
final Object o1 = l1.get(i);
Boolean gotmatch = false;
if (listOrderMatters) {
gotmatch = deepCompare(o1, l2.get(i), listOrderMatters);
} else {
for (int j = 0; j < l2.size(); j++) {
if (!alreadyMatched[j] && deepCompare(o1, l2.get(j), listOrderMatters)) {
alreadyMatched[j] = true;
gotmatch = true;
break;
}
}
}
if (!gotmatch) {
return false;
}
}
return true;
} else {
return v1.equals(v2);
}
}
public static Boolean deepCompare(Object v1, Object v2) {
return deepCompare(v1, v2, false);
}
public static boolean deepContains(List values, Object value) {
for (final Object item : values) {
if (deepCompare(item, value, false)) {
return true;
}
}
return false;
}
static void mergeValue(Map obj, String key, Object value) {
if (obj == null) {
return;
}
List values = (List) obj.get(key);
if (values == null) {
values = new ArrayList();
obj.put(key, values);
}
if ("@list".equals(key)
|| (value instanceof Map && ((Map) value).containsKey("@list"))
|| !deepContains(values, value)) {
values.add(value);
}
}
static void mergeCompactedValue(Map obj, String key, Object value) {
if (obj == null) {
return;
}
final Object prop = obj.get(key);
if (prop == null) {
obj.put(key, value);
return;
}
if (!(prop instanceof List)) {
final List tmp = new ArrayList();
tmp.add(prop);
}
if (value instanceof List) {
((List) prop).addAll((List) value);
} else {
((List) prop).add(value);
}
}
public static boolean isAbsoluteIri(String value) {
// TODO: this is a bit simplistic!
return value.contains(":");
}
/**
* Returns true if the given value is a subject with properties.
*
* @param v
* the value to check.
*
* @return true if the value is a subject with properties, false if not.
*/
static boolean isNode(Object v) {
// Note: A value is a subject if all of these hold true:
// 1. It is an Object.
// 2. It is not a @value, @set, or @list.
// 3. It has more than 1 key OR any existing key is not @id.
if (v instanceof Map
&& !(((Map) v).containsKey("@value") || ((Map) v).containsKey("@set") || ((Map) v)
.containsKey("@list"))) {
return ((Map) v).size() > 1 || !((Map) v).containsKey("@id");
}
return false;
}
/**
* Returns true if the given value is a subject reference.
*
* @param v
* the value to check.
*
* @return true if the value is a subject reference, false if not.
*/
static boolean isNodeReference(Object v) {
// Note: A value is a subject reference if all of these hold true:
// 1. It is an Object.
// 2. It has a single key: @id.
return (v instanceof Map && ((Map) v).size() == 1 && ((Map) v)
.containsKey("@id"));
}
// TODO: fix this test
public static boolean isRelativeIri(String value) {
if (!(isKeyword(value) || isAbsoluteIri(value))) {
return true;
}
return false;
}
// //////////////////////////////////////////////////// OLD CODE BELOW
/**
* Adds a value to a subject. If the value is an array, all values in the
* array will be added.
*
* Note: If the value is a subject that already exists as a property of the
* given subject, this method makes no attempt to deeply merge properties.
* Instead, the value will not be added.
*
* @param subject
* the subject to add the value to.
* @param property
* the property that relates the value to the subject.
* @param value
* the value to add.
* @param [propertyIsArray] true if the property is always an array, false
* if not (default: false).
* @param [allowDuplicate] true if the property is a @list, false if not
* (default: false).
*/
static void addValue(Map subject, String property, Object value,
boolean propertyIsArray, boolean allowDuplicate) {
if (isArray(value)) {
if (((List) value).size() == 0 && propertyIsArray && !subject.containsKey(property)) {
subject.put(property, new ArrayList());
}
for (final Object val : (List) value) {
addValue(subject, property, val, propertyIsArray, allowDuplicate);
}
} else if (subject.containsKey(property)) {
// check if subject already has the value if duplicates not allowed
final boolean hasValue = !allowDuplicate && hasValue(subject, property, value);
// make property an array if value not present or always an array
if (!isArray(subject.get(property)) && (!hasValue || propertyIsArray)) {
final List tmp = new ArrayList();
tmp.add(subject.get(property));
subject.put(property, tmp);
}
// add new value
if (!hasValue) {
((List) subject.get(property)).add(value);
}
} else {
// add new value as a set or single value
Object tmp;
if (propertyIsArray) {
tmp = new ArrayList();
((List) tmp).add(value);
} else {
tmp = value;
}
subject.put(property, tmp);
}
}
static void addValue(Map subject, String property, Object value,
boolean propertyIsArray) {
addValue(subject, property, value, propertyIsArray, true);
}
static void addValue(Map subject, String property, Object value) {
addValue(subject, property, value, false, true);
}
/**
* Prepends a base IRI to the given relative IRI.
*
* @param base
* the base IRI.
* @param iri
* the relative IRI.
*
* @return the absolute IRI.
*
* TODO: the JsonLdUrl class isn't as forgiving as the Node.js url
* parser, we may need to re-implement the parser here to support
* the flexibility required
*/
private static String prependBase(Object baseobj, String iri) {
// already an absolute IRI
if (iri.indexOf(":") != -1) {
return iri;
}
// parse base if it is a string
JsonLdUrl base;
if (isString(baseobj)) {
base = JsonLdUrl.parse((String) baseobj);
} else {
// assume base is already a JsonLdUrl
base = (JsonLdUrl) baseobj;
}
final JsonLdUrl rel = JsonLdUrl.parse(iri);
// start hierarchical part
String hierPart = base.protocol;
if (!"".equals(rel.authority)) {
hierPart += "//" + rel.authority;
} else if (!"".equals(base.href)) {
hierPart += "//" + base.authority;
}
// per RFC3986 normalize
String path;
// IRI represents an absolute path
if (rel.pathname.indexOf("/") == 0) {
path = rel.pathname;
} else {
path = base.pathname;
// append relative path to the end of the last directory from base
if (!"".equals(rel.pathname)) {
path = path.substring(0, path.lastIndexOf("/") + 1);
if (path.length() > 0 && !path.endsWith("/")) {
path += "/";
}
path += rel.pathname;
}
}
// remove slashes anddots in path
path = JsonLdUrl.removeDotSegments(path, !"".equals(hierPart));
// add query and hash
if (!"".equals(rel.query)) {
path += "?" + rel.query;
}
if (!"".equals(rel.hash)) {
path += rel.hash;
}
final String rval = hierPart + path;
if ("".equals(rval)) {
return "./";
}
return rval;
}
/**
* Expands a language map.
*
* @param languageMap
* the language map to expand.
*
* @return the expanded language map.
* @throws JsonLdError
*/
static List expandLanguageMap(Map languageMap) throws JsonLdError {
final List rval = new ArrayList();
final List keys = new ArrayList(languageMap.keySet());
Collections.sort(keys); // lexicographically sort languages
for (final String key : keys) {
List val;
if (!isArray(languageMap.get(key))) {
val = new ArrayList();
val.add(languageMap.get(key));
} else {
val = (List) languageMap.get(key);
}
for (final Object item : val) {
if (!isString(item)) {
throw new JsonLdError(JsonLdError.Error.SYNTAX_ERROR);
}
final Map tmp = new LinkedHashMap();
tmp.put("@value", item);
tmp.put("@language", key.toLowerCase());
rval.add(tmp);
}
}
return rval;
}
/**
* Throws an exception if the given value is not a valid @type value.
*
* @param v
* the value to check.
* @throws JsonLdError
*/
static boolean validateTypeValue(Object v) throws JsonLdError {
if (v == null) {
throw new NullPointerException("\"@type\" value cannot be null");
}
// must be a string, subject reference, or empty object
if (v instanceof String
|| (v instanceof Map && (((Map) v).containsKey("@id") || ((Map) v)
.size() == 0))) {
return true;
}
// must be an array
boolean isValid = false;
if (v instanceof List) {
isValid = true;
for (final Object i : (List) v) {
if (!(i instanceof String || i instanceof Map
&& ((Map) i).containsKey("@id"))) {
isValid = false;
break;
}
}
}
if (!isValid) {
throw new JsonLdError(JsonLdError.Error.SYNTAX_ERROR);
}
return true;
}
/**
* Removes a base IRI from the given absolute IRI.
*
* @param base
* the base IRI.
* @param iri
* the absolute IRI.
*
* @return the relative IRI if relative to base, otherwise the absolute IRI.
*/
private static String removeBase(Object baseobj, String iri) {
JsonLdUrl base;
if (isString(baseobj)) {
base = JsonLdUrl.parse((String) baseobj);
} else {
base = (JsonLdUrl) baseobj;
}
// establish base root
String root = "";
if (!"".equals(base.href)) {
root += (base.protocol) + "//" + base.authority;
}
// support network-path reference with empty base
else if (iri.indexOf("//") != 0) {
root += "//";
}
// IRI not relative to base
if (iri.indexOf(root) != 0) {
return iri;
}
// remove root from IRI and parse remainder
final JsonLdUrl rel = JsonLdUrl.parse(iri.substring(root.length()));
// remove path segments that match
final List baseSegments = _split(base.normalizedPath, "/");
final List iriSegments = _split(rel.normalizedPath, "/");
while (baseSegments.size() > 0 && iriSegments.size() > 0) {
if (!baseSegments.get(0).equals(iriSegments.get(0))) {
break;
}
if (baseSegments.size() > 0) {
baseSegments.remove(0);
}
if (iriSegments.size() > 0) {
iriSegments.remove(0);
}
}
// use '../' for each non-matching base segment
String rval = "";
if (baseSegments.size() > 0) {
// don't count the last segment if it isn't a path (doesn't end in
// '/')
// don't count empty first segment, it means base began with '/'
if (!base.normalizedPath.endsWith("/") || "".equals(baseSegments.get(0))) {
baseSegments.remove(baseSegments.size() - 1);
}
for (int i = 0; i < baseSegments.size(); ++i) {
rval += "../";
}
}
// prepend remaining segments
rval += _join(iriSegments, "/");
// add query and hash
if (!"".equals(rel.query)) {
rval += "?" + rel.query;
}
if (!"".equals(rel.hash)) {
rval += rel.hash;
}
if ("".equals(rval)) {
rval = "./";
}
return rval;
}
/**
* Removes the @preserve keywords as the last step of the framing algorithm.
*
* @param ctx
* the active context used to compact the input.
* @param input
* the framed, compacted output.
* @param options
* the compaction options used.
*
* @return the resulting output.
* @throws JsonLdError
*/
static Object removePreserve(Context ctx, Object input, JsonLdOptions opts) throws JsonLdError {
// recurse through arrays
if (isArray(input)) {
final List output = new ArrayList();
for (final Object i : (List) input) {
final Object result = removePreserve(ctx, i, opts);
// drop nulls from arrays
if (result != null) {
output.add(result);
}
}
input = output;
} else if (isObject(input)) {
// remove @preserve
if (((Map) input).containsKey("@preserve")) {
if ("@null".equals(((Map) input).get("@preserve"))) {
return null;
}
return ((Map) input).get("@preserve");
}
// skip @values
if (isValue(input)) {
return input;
}
// recurse through @lists
if (isList(input)) {
((Map) input).put("@list",
removePreserve(ctx, ((Map) input).get("@list"), opts));
return input;
}
// recurse through properties
for (final String prop : ((Map) input).keySet()) {
Object result = removePreserve(ctx, ((Map) input).get(prop), opts);
final String container = ctx.getContainer(prop);
if (opts.getCompactArrays() && isArray(result)
&& ((List) result).size() == 1 && container == null) {
result = ((List) result).get(0);
}
((Map) input).put(prop, result);
}
}
return input;
}
/**
* replicate javascript .join because i'm too lazy to keep doing it manually
*
* @param iriSegments
* @param string
* @return
*/
private static String _join(List list, String joiner) {
String rval = "";
if (list.size() > 0) {
rval += list.get(0);
}
for (int i = 1; i < list.size(); i++) {
rval += joiner + list.get(i);
}
return rval;
}
/**
* replicates the functionality of javascript .split, which has different
* results to java's String.split if there is a trailing /
*
* @param string
* @param delim
* @return
*/
private static List _split(String string, String delim) {
final List rval = new ArrayList(Arrays.asList(string.split(delim)));
if (string.endsWith("/")) {
// javascript .split includes a blank entry if the string ends with
// the delimiter, java .split does not so we need to add it manually
rval.add("");
}
return rval;
}
/**
* Compares two strings first based on length and then lexicographically.
*
* @param a
* the first string.
* @param b
* the second string.
*
* @return -1 if a < b, 1 if a > b, 0 if a == b.
*/
static int compareShortestLeast(String a, String b) {
if (a.length() < b.length()) {
return -1;
} else if (b.length() < a.length()) {
return 1;
}
return Integer.signum(a.compareTo(b));
}
/**
* Determines if the given value is a property of the given subject.
*
* @param subject
* the subject to check.
* @param property
* the property to check.
* @param value
* the value to check.
*
* @return true if the value exists, false if not.
*/
static boolean hasValue(Map subject, String property, Object value) {
boolean rval = false;
if (hasProperty(subject, property)) {
Object val = subject.get(property);
final boolean isList = isList(val);
if (isList || val instanceof List) {
if (isList) {
val = ((Map) val).get("@list");
}
for (final Object i : (List) val) {
if (compareValues(value, i)) {
rval = true;
break;
}
}
} else if (!(value instanceof List)) {
rval = compareValues(value, val);
}
}
return rval;
}
private static boolean hasProperty(Map subject, String property) {
boolean rval = false;
if (subject.containsKey(property)) {
final Object value = subject.get(property);
rval = (!(value instanceof List) || ((List) value).size() > 0);
}
return rval;
}
/**
* Compares two JSON-LD values for equality. Two JSON-LD values will be
* considered equal if:
*
* 1. They are both primitives of the same type and value. 2. They are both @values
* with the same @value, @type, and @language, OR 3. They both have @ids
* they are the same.
*
* @param v1
* the first value.
* @param v2
* the second value.
*
* @return true if v1 and v2 are considered equal, false if not.
*/
static boolean compareValues(Object v1, Object v2) {
if (v1.equals(v2)) {
return true;
}
if (isValue(v1)
&& isValue(v2)
&& Obj.equals(((Map) v1).get("@value"),
((Map) v2).get("@value"))
&& Obj.equals(((Map) v1).get("@type"),
((Map) v2).get("@type"))
&& Obj.equals(((Map) v1).get("@language"),
((Map) v2).get("@language"))
&& Obj.equals(((Map) v1).get("@index"),
((Map) v2).get("@index"))) {
return true;
}
if ((v1 instanceof Map && ((Map) v1).containsKey("@id"))
&& (v2 instanceof Map && ((Map) v2).containsKey("@id"))
&& ((Map) v1).get("@id").equals(
((Map) v2).get("@id"))) {
return true;
}
return false;
}
/**
* Removes a value from a subject.
*
* @param subject
* the subject.
* @param property
* the property that relates the value to the subject.
* @param value
* the value to remove.
* @param [options] the options to use: [propertyIsArray] true if the
* property is always an array, false if not (default: false).
*/
static void removeValue(Map subject, String property, Map value) {
removeValue(subject, property, value, false);
}
static void removeValue(Map subject, String property,
Map value, boolean propertyIsArray) {
// filter out value
final List values = new ArrayList();
if (subject.get(property) instanceof List) {
for (final Object e : ((List) subject.get(property))) {
if (!(value.equals(e))) {
values.add(value);
}
}
} else {
if (!value.equals(subject.get(property))) {
values.add(subject.get(property));
}
}
if (values.size() == 0) {
subject.remove(property);
} else if (values.size() == 1 && !propertyIsArray) {
subject.put(property, values.get(0));
} else {
subject.put(property, values);
}
}
/**
* Returns true if the given value is a blank node.
*
* @param v
* the value to check.
*
* @return true if the value is a blank node, false if not.
*/
static boolean isBlankNode(Object v) {
// Note: A value is a blank node if all of these hold true:
// 1. It is an Object.
// 2. If it has an @id key its value begins with '_:'.
// 3. It has no keys OR is not a @value, @set, or @list.
if (v instanceof Map) {
if (((Map) v).containsKey("@id")) {
return ((String) ((Map) v).get("@id")).startsWith("_:");
} else {
return ((Map) v).size() == 0
|| !(((Map) v).containsKey("@value") || ((Map) v).containsKey("@set") || ((Map) v)
.containsKey("@list"));
}
}
return false;
}
/**
* Finds all @context URLs in the given JSON-LD input.
*
* @param input
* the JSON-LD input.
* @param urls
* a map of URLs (url => false/@contexts).
* @param replace
* true to replace the URLs in the given input with the
* @contexts from the urls map, false not to.
*
* @return true if new URLs to resolve were found, false if not.
*/
private static boolean findContextUrls(Object input, Map urls, Boolean replace) {
final int count = urls.size();
if (input instanceof List) {
for (final Object i : (List) input) {
findContextUrls(i, urls, replace);
}
return count < urls.size();
} else if (input instanceof Map) {
for (final String key : ((Map) input).keySet()) {
if (!"@context".equals(key)) {
findContextUrls(((Map) input).get(key), urls, replace);
continue;
}
// get @context
final Object ctx = ((Map) input).get(key);
// array @context
if (ctx instanceof List) {
int length = ((List) ctx).size();
for (int i = 0; i < length; i++) {
Object _ctx = ((List) ctx).get(i);
if (_ctx instanceof String) {
// replace w/@context if requested
if (replace) {
_ctx = urls.get(_ctx);
if (_ctx instanceof List) {
// add flattened context
((List) ctx).remove(i);
((List) ctx).addAll((Collection) _ctx);
i += ((List) _ctx).size();
length += ((List) _ctx).size();
} else {
((List) ctx).set(i, _ctx);
}
}
// @context JsonLdUrl found
else if (!urls.containsKey(_ctx)) {
urls.put((String) _ctx, Boolean.FALSE);
}
}
}
}
// string @context
else if (ctx instanceof String) {
// replace w/@context if requested
if (replace) {
((Map) input).put(key, urls.get(ctx));
}
// @context JsonLdUrl found
else if (!urls.containsKey(ctx)) {
urls.put((String) ctx, Boolean.FALSE);
}
}
}
return (count < urls.size());
}
return false;
}
static Object clone(Object value) {// throws
// CloneNotSupportedException {
Object rval = null;
if (value instanceof Cloneable) {
try {
rval = value.getClass().getMethod("clone").invoke(value);
} catch (final Exception e) {
rval = e;
}
}
if (rval == null || rval instanceof Exception) {
// the object wasn't cloneable, or an error occured
if (value == null || value instanceof String || value instanceof Number
|| value instanceof Boolean) {
// strings numbers and booleans are immutable
rval = value;
} else {
// TODO: making this throw runtime exception so it doesn't have
// to be caught
// because simply it should never fail in the case of JSON-LD
// and means that
// the input JSON-LD is invalid
throw new RuntimeException(new CloneNotSupportedException(
(rval instanceof Exception ? ((Exception) rval).getMessage() : "")));
}
}
return rval;
}
/**
* Returns true if the given value is a JSON-LD Array
*
* @param v
* the value to check.
* @return
*/
static Boolean isArray(Object v) {
return (v instanceof List);
}
/**
* Returns true if the given value is a JSON-LD List
*
* @param v
* the value to check.
* @return
*/
static Boolean isList(Object v) {
return (v instanceof Map && ((Map) v).containsKey("@list"));
}
/**
* Returns true if the given value is a JSON-LD Object
*
* @param v
* the value to check.
* @return
*/
static Boolean isObject(Object v) {
return (v instanceof Map);
}
/**
* Returns true if the given value is a JSON-LD value
*
* @param v
* the value to check.
* @return
*/
static Boolean isValue(Object v) {
return (v instanceof Map && ((Map) v).containsKey("@value"));
}
/**
* Returns true if the given value is a JSON-LD string
*
* @param v
* the value to check.
* @return
*/
static Boolean isString(Object v) {
// TODO: should this return true for arrays of strings as well?
return (v instanceof String);
}
}