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.JsonLdApi Maven / Gradle / Ivy
package com.github.jsonldjava.core;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_FIRST;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_LIST;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_NIL;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_REST;
import static com.github.jsonldjava.core.JsonLdConsts.RDF_TYPE;
import static com.github.jsonldjava.core.JsonLdUtils.isKeyword;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.jsonldjava.core.JsonLdError.Error;
import com.github.jsonldjava.utils.Obj;
/**
* A container object to maintain state relating to JsonLdOptions and the
* current Context, and push these into the relevant algorithms in
* JsonLdProcessor as necessary.
*
* @author tristan
*/
public class JsonLdApi {
private final Logger log = LoggerFactory.getLogger(this.getClass());
JsonLdOptions opts;
Object value = null;
Context context = null;
/**
* Constructs an empty JsonLdApi object using the default JsonLdOptions, and
* without initialization.
*/
public JsonLdApi() {
this(new JsonLdOptions(""));
}
/**
* Constructs a JsonLdApi object using the given object as the initial
* JSON-LD object, and the given JsonLdOptions.
*
* @param input
* The initial JSON-LD object.
* @param opts
* The JsonLdOptions to use.
* @throws JsonLdError
* If there is an error initializing using the object and
* options.
*/
public JsonLdApi(Object input, JsonLdOptions opts) throws JsonLdError {
this(opts);
initialize(input, null);
}
/**
* Constructs a JsonLdApi object using the given object as the initial
* JSON-LD object, the given context, and the given JsonLdOptions.
*
* @param input
* The initial JSON-LD object.
* @param context
* The initial context.
* @param opts
* The JsonLdOptions to use.
* @throws JsonLdError
* If there is an error initializing using the object and
* options.
*/
public JsonLdApi(Object input, Object context, JsonLdOptions opts) throws JsonLdError {
this(opts);
initialize(input, null);
}
/**
* Constructs an empty JsonLdApi object using the given JsonLdOptions, and
* without initialization.
* If the JsonLdOptions parameter is null, then the default options are
* used.
*
* @param opts
* The JsonLdOptions to use.
*/
public JsonLdApi(JsonLdOptions opts) {
if (opts == null) {
opts = new JsonLdOptions("");
} else {
this.opts = opts;
}
}
/**
* Initializes this object by cloning the input object using
* {@link JsonLdUtils#clone(Object)}, and by parsing the context using
* {@link Context#parse(Object)}.
*
* @param input
* The initial object, which is to be cloned and used in
* operations.
* @param context
* The context object, which is to be parsed and used in
* operations.
* @throws JsonLdError
* If there was an error cloning the object, or in parsing the
* context.
*/
private void initialize(Object input, Object context) throws JsonLdError {
if (input instanceof List || input instanceof Map) {
this.value = JsonLdUtils.clone(input);
}
// TODO: string/IO input
this.context = new Context(opts);
if (context != null) {
this.context = this.context.parse(context);
}
}
/***
* ____ _ _ _ _ _ _ / ___|___ _ __ ___ _ __ __ _ ___| |_ / \ | | __ _ ___ _
* __(_) |_| |__ _ __ ___ | | / _ \| '_ ` _ \| '_ \ / _` |/ __| __| / _ \ |
* |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ | |__| (_) | | | | | | |_) | (_|
* | (__| |_ / ___ \| | (_| | (_) | | | | |_| | | | | | | | | \____\___/|_|
* |_| |_| .__/ \__,_|\___|\__| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_|
* |_| |_| |___/
*/
/**
* Compaction Algorithm
*
* http://json-ld.org/spec/latest/json-ld-api/#compaction-algorithm
*
* @param activeCtx
* The Active Context
* @param activeProperty
* The Active Property
* @param element
* The current element
* @param compactArrays
* True to compact arrays.
* @return The compacted JSON-LD object.
* @throws JsonLdError
* If there was an error during compaction.
*/
public Object compact(Context activeCtx, String activeProperty, Object element,
boolean compactArrays) throws JsonLdError {
// 2)
if (element instanceof List) {
// 2.1)
final List result = new ArrayList();
// 2.2)
for (final Object item : (List) element) {
// 2.2.1)
final Object compactedItem = compact(activeCtx, activeProperty, item, compactArrays);
// 2.2.2)
if (compactedItem != null) {
result.add(compactedItem);
}
}
// 2.3)
if (compactArrays && result.size() == 1
&& activeCtx.getContainer(activeProperty) == null) {
return result.get(0);
}
// 2.4)
return result;
}
// 3)
if (element instanceof Map) {
// access helper
final Map elem = (Map) element;
// 4
if (elem.containsKey("@value") || elem.containsKey("@id")) {
final Object compactedValue = activeCtx.compactValue(activeProperty, elem);
if (!(compactedValue instanceof Map || compactedValue instanceof List)) {
return compactedValue;
}
}
// 5)
final boolean insideReverse = ("@reverse".equals(activeProperty));
// 6)
final Map result = new LinkedHashMap();
// 7)
final List keys = new ArrayList(elem.keySet());
Collections.sort(keys);
for (final String expandedProperty : keys) {
final Object expandedValue = elem.get(expandedProperty);
// 7.1)
if ("@id".equals(expandedProperty) || "@type".equals(expandedProperty)) {
Object compactedValue;
// 7.1.1)
if (expandedValue instanceof String) {
compactedValue = activeCtx.compactIri((String) expandedValue,
"@type".equals(expandedProperty));
}
// 7.1.2)
else {
final List types = new ArrayList();
// 7.1.2.2)
for (final String expandedType : (List) expandedValue) {
types.add(activeCtx.compactIri(expandedType, true));
}
// 7.1.2.3)
if (types.size() == 1) {
compactedValue = types.get(0);
} else {
compactedValue = types;
}
}
// 7.1.3)
final String alias = activeCtx.compactIri(expandedProperty, true);
// 7.1.4)
result.put(alias, compactedValue);
continue;
// TODO: old add value code, see if it's still relevant?
// addValue(rval, alias, compactedValue,
// isArray(compactedValue)
// && ((List) expandedValue).size() == 0);
}
// 7.2)
if ("@reverse".equals(expandedProperty)) {
// 7.2.1)
final Map compactedValue = (Map) compact(
activeCtx, "@reverse", expandedValue, compactArrays);
// 7.2.2)
// Note: Must create a new set to avoid modifying the set we
// are iterating over
for (final String property : new HashSet(compactedValue.keySet())) {
final Object value = compactedValue.get(property);
// 7.2.2.1)
if (activeCtx.isReverseProperty(property)) {
// 7.2.2.1.1)
if (("@set".equals(activeCtx.getContainer(property)) || !compactArrays)
&& !(value instanceof List)) {
final List tmp = new ArrayList();
tmp.add(value);
result.put(property, tmp);
}
// 7.2.2.1.2)
if (!result.containsKey(property)) {
result.put(property, value);
}
// 7.2.2.1.3)
else {
if (!(result.get(property) instanceof List)) {
final List tmp = new ArrayList();
tmp.add(result.put(property, tmp));
}
if (value instanceof List) {
((List) result.get(property))
.addAll((List) value);
} else {
((List) result.get(property)).add(value);
}
}
// 7.2.2.1.4)
compactedValue.remove(property);
}
}
// 7.2.3)
if (!compactedValue.isEmpty()) {
// 7.2.3.1)
final String alias = activeCtx.compactIri("@reverse", true);
// 7.2.3.2)
result.put(alias, compactedValue);
}
// 7.2.4)
continue;
}
// 7.3)
if ("@index".equals(expandedProperty)
&& "@index".equals(activeCtx.getContainer(activeProperty))) {
continue;
}
// 7.4)
else if ("@index".equals(expandedProperty) || "@value".equals(expandedProperty)
|| "@language".equals(expandedProperty)) {
// 7.4.1)
final String alias = activeCtx.compactIri(expandedProperty, true);
// 7.4.2)
result.put(alias, expandedValue);
continue;
}
// NOTE: expanded value must be an array due to expansion
// algorithm.
// 7.5)
if (((List) expandedValue).size() == 0) {
// 7.5.1)
final String itemActiveProperty = activeCtx.compactIri(expandedProperty,
expandedValue, true, insideReverse);
// 7.5.2)
if (!result.containsKey(itemActiveProperty)) {
result.put(itemActiveProperty, new ArrayList());
} else {
final Object value = result.get(itemActiveProperty);
if (!(value instanceof List)) {
final List tmp = new ArrayList();
tmp.add(value);
result.put(itemActiveProperty, tmp);
}
}
}
// 7.6)
for (final Object expandedItem : (List) expandedValue) {
// 7.6.1)
final String itemActiveProperty = activeCtx.compactIri(expandedProperty,
expandedItem, true, insideReverse);
// 7.6.2)
final String container = activeCtx.getContainer(itemActiveProperty);
// get @list value if appropriate
final boolean isList = (expandedItem instanceof Map && ((Map) expandedItem)
.containsKey("@list"));
Object list = null;
if (isList) {
list = ((Map) expandedItem).get("@list");
}
// 7.6.3)
Object compactedItem = compact(activeCtx, itemActiveProperty, isList ? list
: expandedItem, compactArrays);
// 7.6.4)
if (isList) {
// 7.6.4.1)
if (!(compactedItem instanceof List)) {
final List tmp = new ArrayList();
tmp.add(compactedItem);
compactedItem = tmp;
}
// 7.6.4.2)
if (!"@list".equals(container)) {
// 7.6.4.2.1)
final Map wrapper = new LinkedHashMap();
// TODO: SPEC: no mention of vocab = true
wrapper.put(activeCtx.compactIri("@list", true), compactedItem);
compactedItem = wrapper;
// 7.6.4.2.2)
if (((Map) expandedItem).containsKey("@index")) {
((Map) compactedItem).put(
// TODO: SPEC: no mention of vocab =
// true
activeCtx.compactIri("@index", true),
((Map) expandedItem).get("@index"));
}
}
// 7.6.4.3)
else if (result.containsKey(itemActiveProperty)) {
throw new JsonLdError(Error.COMPACTION_TO_LIST_OF_LISTS,
"There cannot be two list objects associated with an active property that has a container mapping");
}
}
// 7.6.5)
if ("@language".equals(container) || "@index".equals(container)) {
// 7.6.5.1)
Map mapObject;
if (result.containsKey(itemActiveProperty)) {
mapObject = (Map) result.get(itemActiveProperty);
} else {
mapObject = new LinkedHashMap();
result.put(itemActiveProperty, mapObject);
}
// 7.6.5.2)
if ("@language".equals(container)
&& (compactedItem instanceof Map && ((Map) compactedItem)
.containsKey("@value"))) {
compactedItem = ((Map) compactedItem).get("@value");
}
// 7.6.5.3)
final String mapKey = (String) ((Map) expandedItem)
.get(container);
// 7.6.5.4)
if (!mapObject.containsKey(mapKey)) {
mapObject.put(mapKey, compactedItem);
} else {
List tmp;
if (!(mapObject.get(mapKey) instanceof List)) {
tmp = new ArrayList();
tmp.add(mapObject.put(mapKey, tmp));
} else {
tmp = (List) mapObject.get(mapKey);
}
tmp.add(compactedItem);
}
}
// 7.6.6)
else {
// 7.6.6.1)
final Boolean check = (!compactArrays || "@set".equals(container)
|| "@list".equals(container) || "@list".equals(expandedProperty) || "@graph"
.equals(expandedProperty))
&& (!(compactedItem instanceof List));
if (check) {
final List tmp = new ArrayList();
tmp.add(compactedItem);
compactedItem = tmp;
}
// 7.6.6.2)
if (!result.containsKey(itemActiveProperty)) {
result.put(itemActiveProperty, compactedItem);
} else {
if (!(result.get(itemActiveProperty) instanceof List)) {
final List tmp = new ArrayList();
tmp.add(result.put(itemActiveProperty, tmp));
}
if (compactedItem instanceof List) {
((List) result.get(itemActiveProperty))
.addAll((List) compactedItem);
} else {
((List) result.get(itemActiveProperty)).add(compactedItem);
}
}
}
}
}
// 8)
return result;
}
// 2)
return element;
}
/**
* Compaction Algorithm
*
* http://json-ld.org/spec/latest/json-ld-api/#compaction-algorithm
*
* @param activeCtx
* The Active Context
* @param activeProperty
* The Active Property
* @param element
* The current element
* @return The compacted JSON-LD object.
* @throws JsonLdError
* If there was an error during compaction.
*/
public Object compact(Context activeCtx, String activeProperty, Object element)
throws JsonLdError {
return compact(activeCtx, activeProperty, element, true);
}
/***
* _____ _ _ _ _ _ _ | ____|_ ___ __ __ _ _ __ __| | / \ | | __ _ ___ _
* __(_) |_| |__ _ __ ___ | _| \ \/ / '_ \ / _` | '_ \ / _` | / _ \ | |/ _`
* |/ _ \| '__| | __| '_ \| '_ ` _ \ | |___ > <| |_) | (_| | | | | (_| | /
* ___ \| | (_| | (_) | | | | |_| | | | | | | | | |_____/_/\_\ .__/ \__,_|_|
* |_|\__,_| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |_| |___/
*/
/**
* Expansion Algorithm
*
* http://json-ld.org/spec/latest/json-ld-api/#expansion-algorithm
*
* @param activeCtx
* The Active Context
* @param activeProperty
* The Active Property
* @param element
* The current element
* @return The expanded JSON-LD object.
* @throws JsonLdError
* If there was an error during expansion.
*/
public Object expand(Context activeCtx, String activeProperty, Object element)
throws JsonLdError {
// 1)
if (element == null) {
return null;
}
// 3)
if (element instanceof List) {
// 3.1)
final List result = new ArrayList();
// 3.2)
for (final Object item : (List) element) {
// 3.2.1)
final Object v = expand(activeCtx, activeProperty, item);
// 3.2.2)
if (("@list".equals(activeProperty) || "@list".equals(activeCtx
.getContainer(activeProperty)))
&& (v instanceof List || (v instanceof Map && ((Map) v)
.containsKey("@list")))) {
throw new JsonLdError(Error.LIST_OF_LISTS, "lists of lists are not permitted.");
}
// 3.2.3)
else if (v != null) {
if (v instanceof List) {
result.addAll((Collection extends Object>) v);
} else {
result.add(v);
}
}
}
// 3.3)
return result;
}
// 4)
else if (element instanceof Map) {
// access helper
final Map elem = (Map) element;
// 5)
if (elem.containsKey("@context")) {
activeCtx = activeCtx.parse(elem.get("@context"));
}
// 6)
Map result = new LinkedHashMap();
// 7)
final List keys = new ArrayList(elem.keySet());
Collections.sort(keys);
for (final String key : keys) {
final Object value = elem.get(key);
// 7.1)
if (key.equals("@context")) {
continue;
}
// 7.2)
final String expandedProperty = activeCtx.expandIri(key, false, true, null, null);
Object expandedValue = null;
// 7.3)
if (expandedProperty == null
|| (!expandedProperty.contains(":") && !isKeyword(expandedProperty))) {
continue;
}
// 7.4)
if (isKeyword(expandedProperty)) {
// 7.4.1)
if ("@reverse".equals(activeProperty)) {
throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY_MAP,
"a keyword cannot be used as a @reverse propery");
}
// 7.4.2)
if (result.containsKey(expandedProperty)) {
throw new JsonLdError(Error.COLLIDING_KEYWORDS, expandedProperty
+ " already exists in result");
}
// 7.4.3)
if ("@id".equals(expandedProperty)) {
if (!(value instanceof String)) {
throw new JsonLdError(Error.INVALID_ID_VALUE,
"value of @id must be a string");
}
expandedValue = activeCtx
.expandIri((String) value, true, false, null, null);
}
// 7.4.4)
else if ("@type".equals(expandedProperty)) {
if (value instanceof List) {
expandedValue = new ArrayList();
for (final Object v : (List) value) {
if (!(v instanceof String)) {
throw new JsonLdError(Error.INVALID_TYPE_VALUE,
"@type value must be a string or array of strings");
}
((List) expandedValue).add(activeCtx.expandIri((String) v,
true, true, null, null));
}
} else if (value instanceof String) {
expandedValue = activeCtx.expandIri((String) value, true, true, null,
null);
}
// TODO: SPEC: no mention of empty map check
else if (value instanceof Map) {
if (((Map) value).size() != 0) {
throw new JsonLdError(Error.INVALID_TYPE_VALUE,
"@type value must be a an empty object for framing");
}
expandedValue = value;
} else {
throw new JsonLdError(Error.INVALID_TYPE_VALUE,
"@type value must be a string or array of strings");
}
}
// 7.4.5)
else if ("@graph".equals(expandedProperty)) {
expandedValue = expand(activeCtx, "@graph", value);
}
// 7.4.6)
else if ("@value".equals(expandedProperty)) {
if (value != null && (value instanceof Map || value instanceof List)) {
throw new JsonLdError(Error.INVALID_VALUE_OBJECT_VALUE, "value of "
+ expandedProperty + " must be a scalar or null");
}
expandedValue = value;
if (expandedValue == null) {
result.put("@value", null);
continue;
}
}
// 7.4.7)
else if ("@language".equals(expandedProperty)) {
if (!(value instanceof String)) {
throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_STRING, "Value of "
+ expandedProperty + " must be a string");
}
expandedValue = ((String) value).toLowerCase();
}
// 7.4.8)
else if ("@index".equals(expandedProperty)) {
if (!(value instanceof String)) {
throw new JsonLdError(Error.INVALID_INDEX_VALUE, "Value of "
+ expandedProperty + " must be a string");
}
expandedValue = value;
}
// 7.4.9)
else if ("@list".equals(expandedProperty)) {
// 7.4.9.1)
if (activeProperty == null || "@graph".equals(activeProperty)) {
continue;
}
// 7.4.9.2)
expandedValue = expand(activeCtx, activeProperty, value);
// NOTE: step not in the spec yet
if (!(expandedValue instanceof List)) {
final List tmp = new ArrayList();
tmp.add(expandedValue);
expandedValue = tmp;
}
// 7.4.9.3)
for (final Object o : (List) expandedValue) {
if (o instanceof Map && ((Map) o).containsKey("@list")) {
throw new JsonLdError(Error.LIST_OF_LISTS,
"A list may not contain another list");
}
}
}
// 7.4.10)
else if ("@set".equals(expandedProperty)) {
expandedValue = expand(activeCtx, activeProperty, value);
}
// 7.4.11)
else if ("@reverse".equals(expandedProperty)) {
if (!(value instanceof Map)) {
throw new JsonLdError(Error.INVALID_REVERSE_VALUE,
"@reverse value must be an object");
}
// 7.4.11.1)
expandedValue = expand(activeCtx, "@reverse", value);
// NOTE: algorithm assumes the result is a map
// 7.4.11.2)
if (((Map) expandedValue).containsKey("@reverse")) {
final Map reverse = (Map) ((Map) expandedValue)
.get("@reverse");
for (final String property : reverse.keySet()) {
final Object item = reverse.get(property);
// 7.4.11.2.1)
if (!result.containsKey(property)) {
result.put(property, new ArrayList());
}
// 7.4.11.2.2)
if (item instanceof List) {
((List) result.get(property))
.addAll((List) item);
} else {
((List) result.get(property)).add(item);
}
}
}
// 7.4.11.3)
if (((Map) expandedValue).size() > (((Map) expandedValue)
.containsKey("@reverse") ? 1 : 0)) {
// 7.4.11.3.1)
if (!result.containsKey("@reverse")) {
result.put("@reverse", new LinkedHashMap());
}
// 7.4.11.3.2)
final Map reverseMap = (Map) result
.get("@reverse");
// 7.4.11.3.3)
for (final String property : ((Map) expandedValue)
.keySet()) {
if ("@reverse".equals(property)) {
continue;
}
// 7.4.11.3.3.1)
final List items = (List) ((Map) expandedValue)
.get(property);
for (final Object item : items) {
// 7.4.11.3.3.1.1)
if (item instanceof Map
&& (((Map) item).containsKey("@value") || ((Map) item)
.containsKey("@list"))) {
throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY_VALUE);
}
// 7.4.11.3.3.1.2)
if (!reverseMap.containsKey(property)) {
reverseMap.put(property, new ArrayList());
}
// 7.4.11.3.3.1.3)
((List) reverseMap.get(property)).add(item);
}
}
}
// 7.4.11.4)
continue;
}
// TODO: SPEC no mention of @explicit etc in spec
else if ("@explicit".equals(expandedProperty)
|| "@default".equals(expandedProperty)
|| "@embed".equals(expandedProperty)
|| "@embedChildren".equals(expandedProperty)
|| "@omitDefault".equals(expandedProperty)) {
expandedValue = expand(activeCtx, expandedProperty, value);
}
// 7.4.12)
if (expandedValue != null) {
result.put(expandedProperty, expandedValue);
}
// 7.4.13)
continue;
}
// 7.5
else if ("@language".equals(activeCtx.getContainer(key)) && value instanceof Map) {
// 7.5.1)
expandedValue = new ArrayList();
// 7.5.2)
for (final String language : ((Map) value).keySet()) {
Object languageValue = ((Map) value).get(language);
// 7.5.2.1)
if (!(languageValue instanceof List)) {
final Object tmp = languageValue;
languageValue = new ArrayList();
((List) languageValue).add(tmp);
}
// 7.5.2.2)
for (final Object item : (List) languageValue) {
// 7.5.2.2.1)
if (!(item instanceof String)) {
throw new JsonLdError(Error.INVALID_LANGUAGE_MAP_VALUE, "Expected "
+ item.toString() + " to be a string");
}
// 7.5.2.2.2)
final Map tmp = new LinkedHashMap();
tmp.put("@value", item);
tmp.put("@language", language.toLowerCase());
((List) expandedValue).add(tmp);
}
}
}
// 7.6)
else if ("@index".equals(activeCtx.getContainer(key)) && value instanceof Map) {
// 7.6.1)
expandedValue = new ArrayList();
// 7.6.2)
final List indexKeys = new ArrayList(
((Map) value).keySet());
Collections.sort(indexKeys);
for (final String index : indexKeys) {
Object indexValue = ((Map) value).get(index);
// 7.6.2.1)
if (!(indexValue instanceof List)) {
final Object tmp = indexValue;
indexValue = new ArrayList();
((List) indexValue).add(tmp);
}
// 7.6.2.2)
indexValue = expand(activeCtx, key, indexValue);
// 7.6.2.3)
for (final Map item : (List>) indexValue) {
// 7.6.2.3.1)
if (!item.containsKey("@index")) {
item.put("@index", index);
}
// 7.6.2.3.2)
((List) expandedValue).add(item);
}
}
}
// 7.7)
else {
expandedValue = expand(activeCtx, key, value);
}
// 7.8)
if (expandedValue == null) {
continue;
}
// 7.9)
if ("@list".equals(activeCtx.getContainer(key))) {
if (!(expandedValue instanceof Map)
|| !((Map) expandedValue).containsKey("@list")) {
Object tmp = expandedValue;
if (!(tmp instanceof List)) {
tmp = new ArrayList();
((List) tmp).add(expandedValue);
}
expandedValue = new LinkedHashMap();
((Map) expandedValue).put("@list", tmp);
}
}
// 7.10)
if (activeCtx.isReverseProperty(key)) {
// 7.10.1)
if (!result.containsKey("@reverse")) {
result.put("@reverse", new LinkedHashMap());
}
// 7.10.2)
final Map reverseMap = (Map) result
.get("@reverse");
// 7.10.3)
if (!(expandedValue instanceof List)) {
final Object tmp = expandedValue;
expandedValue = new ArrayList();
((List) expandedValue).add(tmp);
}
// 7.10.4)
for (final Object item : (List) expandedValue) {
// 7.10.4.1)
if (item instanceof Map
&& (((Map) item).containsKey("@value") || ((Map) item)
.containsKey("@list"))) {
throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY_VALUE);
}
// 7.10.4.2)
if (!reverseMap.containsKey(expandedProperty)) {
reverseMap.put(expandedProperty, new ArrayList());
}
// 7.10.4.3)
if (item instanceof List) {
((List) reverseMap.get(expandedProperty))
.addAll((List) item);
} else {
((List) reverseMap.get(expandedProperty)).add(item);
}
}
}
// 7.11)
else {
// 7.11.1)
if (!result.containsKey(expandedProperty)) {
result.put(expandedProperty, new ArrayList());
}
// 7.11.2)
if (expandedValue instanceof List) {
((List) result.get(expandedProperty))
.addAll((List) expandedValue);
} else {
((List) result.get(expandedProperty)).add(expandedValue);
}
}
}
// 8)
if (result.containsKey("@value")) {
// 8.1)
// TODO: is this method faster than just using containsKey for
// each?
final Set keySet = new HashSet(result.keySet());
keySet.remove("@value");
keySet.remove("@index");
final boolean langremoved = keySet.remove("@language");
final boolean typeremoved = keySet.remove("@type");
if ((langremoved && typeremoved) || !keySet.isEmpty()) {
throw new JsonLdError(Error.INVALID_VALUE_OBJECT,
"value object has unknown keys");
}
// 8.2)
final Object rval = result.get("@value");
if (rval == null) {
// nothing else is possible with result if we set it to
// null, so simply return it
return null;
}
// 8.3)
if (!(rval instanceof String) && result.containsKey("@language")) {
throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_VALUE,
"when @language is used, @value must be a string");
}
// 8.4)
else if (result.containsKey("@type")) {
// TODO: is this enough for "is an IRI"
if (!(result.get("@type") instanceof String)
|| ((String) result.get("@type")).startsWith("_:")
|| !((String) result.get("@type")).contains(":")) {
throw new JsonLdError(Error.INVALID_TYPED_VALUE,
"value of @type must be an IRI");
}
}
}
// 9)
else if (result.containsKey("@type")) {
final Object rtype = result.get("@type");
if (!(rtype instanceof List)) {
final List tmp = new ArrayList();
tmp.add(rtype);
result.put("@type", tmp);
}
}
// 10)
else if (result.containsKey("@set") || result.containsKey("@list")) {
// 10.1)
if (result.size() > (result.containsKey("@index") ? 2 : 1)) {
throw new JsonLdError(Error.INVALID_SET_OR_LIST_OBJECT,
"@set or @list may only contain @index");
}
// 10.2)
if (result.containsKey("@set")) {
// result becomes an array here, thus the remaining checks
// will never be true from here on
// so simply return the value rather than have to make
// result an object and cast it with every
// other use in the function.
return result.get("@set");
}
}
// 11)
if (result.containsKey("@language") && result.size() == 1) {
result = null;
}
// 12)
if (activeProperty == null || "@graph".equals(activeProperty)) {
// 12.1)
if (result != null
&& (result.size() == 0 || result.containsKey("@value") || result
.containsKey("@list"))) {
result = null;
}
// 12.2)
else if (result != null && result.containsKey("@id") && result.size() == 1) {
result = null;
}
}
// 13)
return result;
}
// 2) If element is a scalar
else {
// 2.1)
if (activeProperty == null || "@graph".equals(activeProperty)) {
return null;
}
return activeCtx.expandValue(activeProperty, element);
}
}
/**
* Expansion Algorithm
*
* http://json-ld.org/spec/latest/json-ld-api/#expansion-algorithm
*
* @param activeCtx
* The Active Context
* @param element
* The current element
* @return The expanded JSON-LD object.
* @throws JsonLdError
* If there was an error during expansion.
*/
public Object expand(Context activeCtx, Object element) throws JsonLdError {
return expand(activeCtx, null, element);
}
/***
* _____ _ _ _ _ _ _ _ _ | ___| | __ _| |_| |_ ___ _ __ / \ | | __ _ ___ _
* __(_) |_| |__ _ __ ___ | |_ | |/ _` | __| __/ _ \ '_ \ / _ \ | |/ _` |/ _
* \| '__| | __| '_ \| '_ ` _ \ | _| | | (_| | |_| || __/ | | | / ___ \| |
* (_| | (_) | | | | |_| | | | | | | | | |_| |_|\__,_|\__|\__\___|_| |_| /_/
* \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| |___/
*/
void generateNodeMap(Object element, Map nodeMap) throws JsonLdError {
generateNodeMap(element, nodeMap, "@default", null, null, null);
}
void generateNodeMap(Object element, Map nodeMap, String activeGraph)
throws JsonLdError {
generateNodeMap(element, nodeMap, activeGraph, null, null, null);
}
void generateNodeMap(Object element, Map nodeMap, String activeGraph,
Object activeSubject, String activeProperty, Map list)
throws JsonLdError {
// 1)
if (element instanceof List) {
// 1.1)
for (final Object item : (List) element) {
generateNodeMap(item, nodeMap, activeGraph, activeSubject, activeProperty, list);
}
return;
}
// for convenience
final Map elem = (Map) element;
// 2)
if (!nodeMap.containsKey(activeGraph)) {
nodeMap.put(activeGraph, new LinkedHashMap());
}
final Map graph = (Map) nodeMap.get(activeGraph);
Map node = (Map) (activeSubject == null ? null : graph
.get(activeSubject));
// 3)
if (elem.containsKey("@type")) {
// 3.1)
List oldTypes;
final List newTypes = new ArrayList();
if (elem.get("@type") instanceof List) {
oldTypes = (List) elem.get("@type");
} else {
oldTypes = new ArrayList();
oldTypes.add((String) elem.get("@type"));
}
for (final String item : oldTypes) {
if (item.startsWith("_:")) {
newTypes.add(generateBlankNodeIdentifier(item));
} else {
newTypes.add(item);
}
}
if (elem.get("@type") instanceof List) {
elem.put("@type", newTypes);
} else {
elem.put("@type", newTypes.get(0));
}
}
// 4)
if (elem.containsKey("@value")) {
// 4.1)
if (list == null) {
JsonLdUtils.mergeValue(node, activeProperty, elem);
}
// 4.2)
else {
JsonLdUtils.mergeValue(list, "@list", elem);
}
}
// 5)
else if (elem.containsKey("@list")) {
// 5.1)
final Map result = new LinkedHashMap();
result.put("@list", new ArrayList());
// 5.2)
// for (final Object item : (List) elem.get("@list")) {
// generateNodeMap(item, nodeMap, activeGraph, activeSubject,
// activeProperty, result);
// }
generateNodeMap(elem.get("@list"), nodeMap, activeGraph, activeSubject, activeProperty,
result);
// 5.3)
JsonLdUtils.mergeValue(node, activeProperty, result);
}
// 6)
else {
// 6.1)
String id = (String) elem.remove("@id");
if (id != null) {
if (id.startsWith("_:")) {
id = generateBlankNodeIdentifier(id);
}
}
// 6.2)
else {
id = generateBlankNodeIdentifier(null);
}
// 6.3)
if (!graph.containsKey(id)) {
final Map tmp = new LinkedHashMap();
tmp.put("@id", id);
graph.put(id, tmp);
}
// 6.4) TODO: SPEC this line is asked for by the spec, but it breaks
// various tests
// node = (Map) graph.get(id);
// 6.5)
if (activeSubject instanceof Map) {
// 6.5.1)
JsonLdUtils.mergeValue((Map) graph.get(id), activeProperty,
activeSubject);
}
// 6.6)
else if (activeProperty != null) {
final Map reference = new LinkedHashMap();
reference.put("@id", id);
// 6.6.2)
if (list == null) {
// 6.6.2.1+2)
JsonLdUtils.mergeValue(node, activeProperty, reference);
}
// 6.6.3) TODO: SPEC says to add ELEMENT to @list member, should
// be REFERENCE
else {
JsonLdUtils.mergeValue(list, "@list", reference);
}
}
// TODO: SPEC this is removed in the spec now, but it's still needed
// (see 6.4)
node = (Map) graph.get(id);
// 6.7)
if (elem.containsKey("@type")) {
for (final Object type : (List) elem.remove("@type")) {
JsonLdUtils.mergeValue(node, "@type", type);
}
}
// 6.8)
if (elem.containsKey("@index")) {
final Object elemIndex = elem.remove("@index");
if (node.containsKey("@index")) {
if (!JsonLdUtils.deepCompare(node.get("@index"), elemIndex)) {
throw new JsonLdError(Error.CONFLICTING_INDEXES);
}
} else {
node.put("@index", elemIndex);
}
}
// 6.9)
if (elem.containsKey("@reverse")) {
// 6.9.1)
final Map referencedNode = new LinkedHashMap();
referencedNode.put("@id", id);
// 6.9.2+6.9.4)
final Map reverseMap = (Map) elem
.remove("@reverse");
// 6.9.3)
for (final String property : reverseMap.keySet()) {
final List values = (List) reverseMap.get(property);
// 6.9.3.1)
for (final Object value : values) {
// 6.9.3.1.1)
generateNodeMap(value, nodeMap, activeGraph, referencedNode, property, null);
}
}
}
// 6.10)
if (elem.containsKey("@graph")) {
generateNodeMap(elem.remove("@graph"), nodeMap, id, null, null, null);
}
// 6.11)
final List keys = new ArrayList(elem.keySet());
Collections.sort(keys);
for (String property : keys) {
final Object value = elem.get(property);
// 6.11.1)
if (property.startsWith("_:")) {
property = generateBlankNodeIdentifier(property);
}
// 6.11.2)
if (!node.containsKey(property)) {
node.put(property, new ArrayList());
}
// 6.11.3)
generateNodeMap(value, nodeMap, activeGraph, id, property, null);
}
}
}
/**
* Blank Node identifier map specified in:
*
* http://www.w3.org/TR/json-ld-api/#generate-blank-node-identifier
*/
private final Map blankNodeIdentifierMap = new LinkedHashMap();
/**
* Counter specified in:
*
* http://www.w3.org/TR/json-ld-api/#generate-blank-node-identifier
*/
private int blankNodeCounter = 0;
/**
* Generates a blank node identifier for the given key using the algorithm
* specified in:
*
* http://www.w3.org/TR/json-ld-api/#generate-blank-node-identifier
*
* @param id
* The id, or null to generate a fresh, unused, blank node
* identifier.
* @return A blank node identifier based on id if it was not null, or a
* fresh, unused, blank node identifier if it was null.
*/
String generateBlankNodeIdentifier(String id) {
if (id != null && blankNodeIdentifierMap.containsKey(id)) {
return blankNodeIdentifierMap.get(id);
}
final String bnid = "_:b" + blankNodeCounter++;
if (id != null) {
blankNodeIdentifierMap.put(id, bnid);
}
return bnid;
}
/**
* Generates a fresh, unused, blank node identifier using the algorithm
* specified in:
*
* http://www.w3.org/TR/json-ld-api/#generate-blank-node-identifier
*
* @return A fresh, unused, blank node identifier.
*/
String generateBlankNodeIdentifier() {
return generateBlankNodeIdentifier(null);
}
/***
* _____ _ _ _ _ _ _ | ___| __ __ _ _ __ ___ (_)_ __ __ _ / \ | | __ _ ___ _
* __(_) |_| |__ _ __ ___ | |_ | '__/ _` | '_ ` _ \| | '_ \ / _` | / _ \ |
* |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ | _|| | | (_| | | | | | | | | | |
* (_| | / ___ \| | (_| | (_) | | | | |_| | | | | | | | | |_| |_| \__,_|_|
* |_| |_|_|_| |_|\__, | /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
* |___/ |___/
*/
private class FramingContext {
public boolean embed;
public boolean explicit;
public boolean omitDefault;
public FramingContext() {
embed = true;
explicit = false;
omitDefault = false;
embeds = null;
}
public FramingContext(JsonLdOptions opts) {
this();
if (opts.getEmbed() != null) {
this.embed = opts.getEmbed();
}
if (opts.getExplicit() != null) {
this.explicit = opts.getExplicit();
}
if (opts.getOmitDefault() != null) {
this.omitDefault = opts.getOmitDefault();
}
}
public Map embeds = null;
}
private class EmbedNode {
public Object parent = null;
public String property = null;
}
private Map nodeMap;
/**
* Performs JSON-LD framing .
*
* @param input
* the expanded JSON-LD to frame.
* @param frame
* the expanded JSON-LD frame to use.
* @return the framed output.
* @throws JsonLdError
* If the framing was not successful.
*/
public List frame(Object input, List frame) throws JsonLdError {
// create framing state
final FramingContext state = new FramingContext(this.opts);
// use tree map so keys are sotred by default
final Map nodes = new TreeMap();
generateNodeMap(input, nodes);
this.nodeMap = (Map) nodes.get("@default");
final List framed = new ArrayList();
// NOTE: frame validation is done by the function not allowing anything
// other than list to me passed
frame(state, this.nodeMap,
(frame != null && frame.size() > 0 ? (Map) frame.get(0)
: new LinkedHashMap()), framed, null);
return framed;
}
/**
* Frames subjects according to the given frame.
*
* @param state
* the current framing state.
* @param subjects
* the subjects to filter.
* @param frame
* the frame.
* @param parent
* the parent subject or top-level array.
* @param property
* the parent property, initialized to null.
* @throws JsonLdError
* If there was an error during framing.
*/
private void frame(FramingContext state, Map nodes, Map frame,
Object parent, String property) throws JsonLdError {
// filter out subjects that match the frame
final Map matches = filterNodes(state, nodes, frame);
// get flags for current frame
Boolean embedOn = getFrameFlag(frame, "@embed", state.embed);
final Boolean explicicOn = getFrameFlag(frame, "@explicit", state.explicit);
// add matches to output
final List ids = new ArrayList(matches.keySet());
Collections.sort(ids);
for (final String id : ids) {
if (property == null) {
state.embeds = new LinkedHashMap();
}
// start output
final Map output = new LinkedHashMap();
output.put("@id", id);
// prepare embed meta info
final EmbedNode embeddedNode = new EmbedNode();
embeddedNode.parent = parent;
embeddedNode.property = property;
// if embed is on and there is an existing embed
if (embedOn && state.embeds.containsKey(id)) {
final EmbedNode existing = state.embeds.get(id);
embedOn = false;
if (existing.parent instanceof List) {
for (final Object p : (List) existing.parent) {
if (JsonLdUtils.compareValues(output, p)) {
embedOn = true;
break;
}
}
}
// existing embed's parent is an object
else {
if (((Map) existing.parent).containsKey(existing.property)) {
for (final Object v : (List) ((Map) existing.parent)
.get(existing.property)) {
if (v instanceof Map
&& Obj.equals(id, ((Map) v).get("@id"))) {
embedOn = true;
break;
}
}
}
}
// existing embed has already been added, so allow an overwrite
if (embedOn) {
removeEmbed(state, id);
}
}
// not embedding, add output without any other properties
if (!embedOn) {
addFrameOutput(state, parent, property, output);
} else {
// add embed meta info
state.embeds.put(id, embeddedNode);
// iterate over subject properties
final Map element = (Map) matches.get(id);
List props = new ArrayList(element.keySet());
Collections.sort(props);
for (final String prop : props) {
// copy keywords to output
if (isKeyword(prop)) {
output.put(prop, JsonLdUtils.clone(element.get(prop)));
continue;
}
// if property isn't in the frame
if (!frame.containsKey(prop)) {
// if explicit is off, embed values
if (!explicicOn) {
embedValues(state, element, prop, output);
}
continue;
}
// add objects
final List value = (List) element.get(prop);
for (final Object item : value) {
// recurse into list
if ((item instanceof Map)
&& ((Map) item).containsKey("@list")) {
// add empty list
final Map list = new LinkedHashMap();
list.put("@list", new ArrayList());
addFrameOutput(state, output, prop, list);
// add list objects
for (final Object listitem : (List) ((Map) item)
.get("@list")) {
// recurse into subject reference
if (JsonLdUtils.isNodeReference(listitem)) {
final Map tmp = new LinkedHashMap();
final String itemid = (String) ((Map) listitem)
.get("@id");
// TODO: nodes may need to be node_map,
// which is global
tmp.put(itemid, this.nodeMap.get(itemid));
frame(state, tmp,
(Map) ((List) frame.get(prop))
.get(0), list, "@list");
} else {
// include other values automatcially (TODO:
// may need JsonLdUtils.clone(n))
addFrameOutput(state, list, "@list", listitem);
}
}
}
// recurse into subject reference
else if (JsonLdUtils.isNodeReference(item)) {
final Map tmp = new LinkedHashMap();
final String itemid = (String) ((Map) item).get("@id");
// TODO: nodes may need to be node_map, which is
// global
tmp.put(itemid, this.nodeMap.get(itemid));
frame(state, tmp,
(Map) ((List) frame.get(prop)).get(0),
output, prop);
} else {
// include other values automatically (TODO: may
// need JsonLdUtils.clone(o))
addFrameOutput(state, output, prop, item);
}
}
}
// handle defaults
props = new ArrayList(frame.keySet());
Collections.sort(props);
for (final String prop : props) {
// skip keywords
if (isKeyword(prop)) {
continue;
}
final List pf = (List) frame.get(prop);
Map propertyFrame = pf.size() > 0 ? (Map) pf
.get(0) : null;
if (propertyFrame == null) {
propertyFrame = new LinkedHashMap();
}
final boolean omitDefaultOn = getFrameFlag(propertyFrame, "@omitDefault",
state.omitDefault);
if (!omitDefaultOn && !output.containsKey(prop)) {
Object def = "@null";
if (propertyFrame.containsKey("@default")) {
def = JsonLdUtils.clone(propertyFrame.get("@default"));
}
if (!(def instanceof List)) {
final List tmp = new ArrayList();
tmp.add(def);
def = tmp;
}
final Map tmp1 = new LinkedHashMap();
tmp1.put("@preserve", def);
final List tmp2 = new ArrayList();
tmp2.add(tmp1);
output.put(prop, tmp2);
}
}
// add output to parent
addFrameOutput(state, parent, property, output);
}
}
}
private Boolean getFrameFlag(Map frame, String name, boolean thedefault) {
Object value = frame.get(name);
if (value instanceof List) {
if (((List) value).size() > 0) {
value = ((List) value).get(0);
}
}
if (value instanceof Map && ((Map) value).containsKey("@value")) {
value = ((Map) value).get("@value");
}
if (value instanceof Boolean) {
return (Boolean) value;
}
return thedefault;
}
/**
* Removes an existing embed.
*
* @param state
* the current framing state.
* @param id
* the @id of the embed to remove.
*/
private static void removeEmbed(FramingContext state, String id) {
// get existing embed
final Map embeds = state.embeds;
final EmbedNode embed = embeds.get(id);
final Object parent = embed.parent;
final String property = embed.property;
// create reference to replace embed
final Map node = new LinkedHashMap();
node.put("@id", id);
// remove existing embed
if (JsonLdUtils.isNode(parent)) {
// replace subject with reference
final List newvals = new ArrayList();
final List oldvals = (List) ((Map) parent)
.get(property);
for (final Object v : oldvals) {
if (v instanceof Map && Obj.equals(((Map) v).get("@id"), id)) {
newvals.add(node);
} else {
newvals.add(v);
}
}
((Map) parent).put(property, newvals);
}
// recursively remove dependent dangling embeds
removeDependents(embeds, id);
}
private static void removeDependents(Map embeds, String id) {
// get embed keys as a separate array to enable deleting keys in map
for (final String id_dep : embeds.keySet()) {
final EmbedNode e = embeds.get(id_dep);
final Object p = e.parent != null ? e.parent : new LinkedHashMap();
if (!(p instanceof Map)) {
continue;
}
final String pid = (String) ((Map) p).get("@id");
if (Obj.equals(id, pid)) {
embeds.remove(id_dep);
removeDependents(embeds, id_dep);
}
}
}
private Map filterNodes(FramingContext state, Map