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
/*
* Copyright (c) 2012, Deutsche Forschungszentrum für Künstliche Intelligenz GmbH
* Copyright (c) 2012-2017, JSONLD-Java contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
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 static com.github.jsonldjava.utils.Obj.newMap;
import com.github.jsonldjava.core.JsonLdError.Error;
import com.github.jsonldjava.utils.Obj;
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;
/**
* 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.
*/
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.
*/
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.
*/
@SuppressWarnings("unchecked")
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(JsonLdConsts.VALUE) || elem.containsKey(JsonLdConsts.ID)) {
final Object compactedValue = activeCtx.compactValue(activeProperty, elem);
if (!(compactedValue instanceof Map || compactedValue instanceof List)) {
return compactedValue;
}
}
// 5)
final boolean insideReverse = (JsonLdConsts.REVERSE.equals(activeProperty));
// 6)
final Map result = newMap();
// 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 (JsonLdConsts.ID.equals(expandedProperty)
|| JsonLdConsts.TYPE.equals(expandedProperty)) {
Object compactedValue;
// 7.1.1)
if (expandedValue instanceof String) {
compactedValue = activeCtx.compactIri((String) expandedValue,
JsonLdConsts.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 (JsonLdConsts.REVERSE.equals(expandedProperty)) {
// 7.2.1)
final Map compactedValue = (Map) compact(
activeCtx, JsonLdConsts.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 ((JsonLdConsts.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(JsonLdConsts.REVERSE, true);
// 7.2.3.2)
result.put(alias, compactedValue);
}
// 7.2.4)
continue;
}
// 7.3)
if (JsonLdConsts.INDEX.equals(expandedProperty)
&& JsonLdConsts.INDEX.equals(activeCtx.getContainer(activeProperty))) {
continue;
}
// 7.4)
else if (JsonLdConsts.INDEX.equals(expandedProperty)
|| JsonLdConsts.VALUE.equals(expandedProperty)
|| JsonLdConsts.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(JsonLdConsts.LIST));
Object list = null;
if (isList) {
list = ((Map) expandedItem).get(JsonLdConsts.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 (!JsonLdConsts.LIST.equals(container)) {
// 7.6.4.2.1)
final Map wrapper = newMap();
// TODO: SPEC: no mention of vocab = true
wrapper.put(activeCtx.compactIri(JsonLdConsts.LIST, true),
compactedItem);
compactedItem = wrapper;
// 7.6.4.2.2)
if (((Map) expandedItem)
.containsKey(JsonLdConsts.INDEX)) {
((Map) compactedItem).put(
// TODO: SPEC: no mention of vocab =
// true
activeCtx.compactIri(JsonLdConsts.INDEX, true),
((Map) expandedItem)
.get(JsonLdConsts.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 (JsonLdConsts.LANGUAGE.equals(container)
|| JsonLdConsts.INDEX.equals(container)) {
// 7.6.5.1)
Map mapObject;
if (result.containsKey(itemActiveProperty)) {
mapObject = (Map) result.get(itemActiveProperty);
} else {
mapObject = newMap();
result.put(itemActiveProperty, mapObject);
}
// 7.6.5.2)
if (JsonLdConsts.LANGUAGE.equals(container) && (compactedItem instanceof Map
&& ((Map) compactedItem)
.containsKey(JsonLdConsts.VALUE))) {
compactedItem = ((Map) compactedItem)
.get(JsonLdConsts.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 || JsonLdConsts.SET.equals(container)
|| JsonLdConsts.LIST.equals(container)
|| JsonLdConsts.LIST.equals(expandedProperty)
|| JsonLdConsts.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, JsonLdOptions.DEFAULT_COMPACT_ARRAYS);
}
/**
* 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.
*/
@SuppressWarnings("unchecked")
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 ((JsonLdConsts.LIST.equals(activeProperty)
|| JsonLdConsts.LIST.equals(activeCtx.getContainer(activeProperty)))
&& (v instanceof List || (v instanceof Map
&& ((Map) v).containsKey(JsonLdConsts.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>) 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(JsonLdConsts.CONTEXT)) {
activeCtx = activeCtx.parse(elem.get(JsonLdConsts.CONTEXT));
}
// 6)
Map result = newMap();
// 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(JsonLdConsts.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 (JsonLdConsts.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 (JsonLdConsts.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 (JsonLdConsts.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 (JsonLdConsts.GRAPH.equals(expandedProperty)) {
expandedValue = expand(activeCtx, JsonLdConsts.GRAPH, value);
}
// 7.4.6)
else if (JsonLdConsts.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(JsonLdConsts.VALUE, null);
continue;
}
}
// 7.4.7)
else if (JsonLdConsts.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 (JsonLdConsts.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 (JsonLdConsts.LIST.equals(expandedProperty)) {
// 7.4.9.1)
if (activeProperty == null || JsonLdConsts.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(JsonLdConsts.LIST)) {
throw new JsonLdError(Error.LIST_OF_LISTS,
"A list may not contain another list");
}
}
}
// 7.4.10)
else if (JsonLdConsts.SET.equals(expandedProperty)) {
expandedValue = expand(activeCtx, activeProperty, value);
}
// 7.4.11)
else if (JsonLdConsts.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, JsonLdConsts.REVERSE, value);
// NOTE: algorithm assumes the result is a map
// 7.4.11.2)
if (((Map) expandedValue)
.containsKey(JsonLdConsts.REVERSE)) {
final Map reverse = (Map) (
(Map)
expandedValue)
.get(JsonLdConsts.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(JsonLdConsts.REVERSE) ? 1 : 0)) {
// 7.4.11.3.1)
if (!result.containsKey(JsonLdConsts.REVERSE)) {
result.put(JsonLdConsts.REVERSE, newMap());
}
// 7.4.11.3.2)
final Map reverseMap = (Map) result
.get(JsonLdConsts.REVERSE);
// 7.4.11.3.3)
for (final String property : ((Map) expandedValue)
.keySet()) {
if (JsonLdConsts.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(JsonLdConsts.VALUE)
|| ((Map) item)
.containsKey(JsonLdConsts.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 (JsonLdConsts.EXPLICIT.equals(expandedProperty)
|| JsonLdConsts.DEFAULT.equals(expandedProperty)
|| JsonLdConsts.EMBED.equals(expandedProperty)
|| JsonLdConsts.EMBED_CHILDREN.equals(expandedProperty)
|| JsonLdConsts.OMIT_DEFAULT.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 (JsonLdConsts.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 = newMap();
tmp.put(JsonLdConsts.VALUE, item);
tmp.put(JsonLdConsts.LANGUAGE, language.toLowerCase());
((List) expandedValue).add(tmp);
}
}
}
// 7.6)
else if (JsonLdConsts.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(JsonLdConsts.INDEX)) {
item.put(JsonLdConsts.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 (JsonLdConsts.LIST.equals(activeCtx.getContainer(key))) {
if (!(expandedValue instanceof Map) || !((Map) expandedValue)
.containsKey(JsonLdConsts.LIST)) {
Object tmp = expandedValue;
if (!(tmp instanceof List)) {
tmp = new ArrayList();
((List) tmp).add(expandedValue);
}
expandedValue = newMap();
((Map) expandedValue).put(JsonLdConsts.LIST, tmp);
}
}
// 7.10)
if (activeCtx.isReverseProperty(key)) {
// 7.10.1)
if (!result.containsKey(JsonLdConsts.REVERSE)) {
result.put(JsonLdConsts.REVERSE, newMap());
}
// 7.10.2)
final Map reverseMap = (Map) result
.get(JsonLdConsts.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(JsonLdConsts.VALUE)
|| ((Map) item).containsKey(JsonLdConsts.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(JsonLdConsts.VALUE)) {
// 8.1)
// TODO: is this method faster than just using containsKey for
// each?
final Set keySet = new HashSet(result.keySet());
keySet.remove(JsonLdConsts.VALUE);
keySet.remove(JsonLdConsts.INDEX);
final boolean langremoved = keySet.remove(JsonLdConsts.LANGUAGE);
final boolean typeremoved = keySet.remove(JsonLdConsts.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(JsonLdConsts.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(JsonLdConsts.LANGUAGE)) {
throw new JsonLdError(Error.INVALID_LANGUAGE_TAGGED_VALUE,
"when @language is used, @value must be a string");
}
// 8.4)
else if (result.containsKey(JsonLdConsts.TYPE)) {
// TODO: is this enough for "is an IRI"
if (!(result.get(JsonLdConsts.TYPE) instanceof String)
|| ((String) result.get(JsonLdConsts.TYPE)).startsWith("_:")
|| !((String) result.get(JsonLdConsts.TYPE)).contains(":")) {
throw new JsonLdError(Error.INVALID_TYPED_VALUE,
"value of @type must be an IRI");
}
}
}
// 9)
else if (result.containsKey(JsonLdConsts.TYPE)) {
final Object rtype = result.get(JsonLdConsts.TYPE);
if (!(rtype instanceof List)) {
final List tmp = new ArrayList<>();
tmp.add(rtype);
result.put(JsonLdConsts.TYPE, tmp);
}
}
// 10)
else if (result.containsKey(JsonLdConsts.SET)
|| result.containsKey(JsonLdConsts.LIST)) {
// 10.1)
if (result.size() > (result.containsKey(JsonLdConsts.INDEX) ? 2 : 1)) {
throw new JsonLdError(Error.INVALID_SET_OR_LIST_OBJECT,
"@set or @list may only contain @index");
}
// 10.2)
if (result.containsKey(JsonLdConsts.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(JsonLdConsts.SET);
}
}
// 11)
if (result.containsKey(JsonLdConsts.LANGUAGE) && result.size() == 1) {
result = null;
}
// 12)
if (activeProperty == null || JsonLdConsts.GRAPH.equals(activeProperty)) {
// 12.1)
if (result != null && (result.size() == 0 || result.containsKey(JsonLdConsts.VALUE)
|| result.containsKey(JsonLdConsts.LIST))) {
result = null;
}
// 12.2)
else if (result != null && result.containsKey(JsonLdConsts.ID)
&& result.size() == 1) {
result = null;
}
}
// 13)
return result;
}
// 2) If element is a scalar
else {
// 2.1)
if (activeProperty == null || JsonLdConsts.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, JsonLdConsts.DEFAULT, null, null, null);
}
void generateNodeMap(Object element, Map nodeMap, String activeGraph)
throws JsonLdError {
generateNodeMap(element, nodeMap, activeGraph, null, null, null);
}
@SuppressWarnings("unchecked")
private 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, newMap());
}
final Map graph = (Map) nodeMap.get(activeGraph);
Map node = (Map) (activeSubject == null ? null
: graph.get(activeSubject));
// 3)
if (elem.containsKey(JsonLdConsts.TYPE)) {
// 3.1)
List oldTypes;
final List newTypes = new ArrayList<>();
if (elem.get(JsonLdConsts.TYPE) instanceof List) {
oldTypes = (List) elem.get(JsonLdConsts.TYPE);
} else {
oldTypes = new ArrayList<>(4);
oldTypes.add((String) elem.get(JsonLdConsts.TYPE));
}
for (final String item : oldTypes) {
if (item.startsWith("_:")) {
newTypes.add(generateBlankNodeIdentifier(item));
} else {
newTypes.add(item);
}
}
if (elem.get(JsonLdConsts.TYPE) instanceof List) {
elem.put(JsonLdConsts.TYPE, newTypes);
} else {
elem.put(JsonLdConsts.TYPE, newTypes.get(0));
}
}
// 4)
if (elem.containsKey(JsonLdConsts.VALUE)) {
// 4.1)
if (list == null) {
JsonLdUtils.mergeValue(node, activeProperty, elem);
}
// 4.2)
else {
JsonLdUtils.mergeValue(list, JsonLdConsts.LIST, elem);
}
}
// 5)
else if (elem.containsKey(JsonLdConsts.LIST)) {
// 5.1)
final Map result = newMap(JsonLdConsts.LIST, new ArrayList<>(4));
// 5.2)
// for (final Object item : (List) elem.get("@list")) {
// generateNodeMap(item, nodeMap, activeGraph, activeSubject,
// activeProperty, result);
// }
generateNodeMap(elem.get(JsonLdConsts.LIST), nodeMap, activeGraph, activeSubject,
activeProperty, result);
// 5.3)
JsonLdUtils.mergeValue(node, activeProperty, result);
}
// 6)
else {
// 6.1)
String id = (String) elem.remove(JsonLdConsts.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 = newMap(JsonLdConsts.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 = newMap(JsonLdConsts.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, JsonLdConsts.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(JsonLdConsts.TYPE)) {
for (final Object type : (List) elem.remove(JsonLdConsts.TYPE)) {
JsonLdUtils.mergeValue(node, JsonLdConsts.TYPE, type);
}
}
// 6.8)
if (elem.containsKey(JsonLdConsts.INDEX)) {
final Object elemIndex = elem.remove(JsonLdConsts.INDEX);
if (node.containsKey(JsonLdConsts.INDEX)) {
if (!JsonLdUtils.deepCompare(node.get(JsonLdConsts.INDEX), elemIndex)) {
throw new JsonLdError(Error.CONFLICTING_INDEXES);
}
} else {
node.put(JsonLdConsts.INDEX, elemIndex);
}
}
// 6.9)
if (elem.containsKey(JsonLdConsts.REVERSE)) {
// 6.9.1)
final Map referencedNode = newMap(JsonLdConsts.ID, id);
// 6.9.2+6.9.4)
final Map reverseMap = (Map) elem
.remove(JsonLdConsts.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(JsonLdConsts.GRAPH)) {
generateNodeMap(elem.remove(JsonLdConsts.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<>(4));
}
// 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.
*/
private 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;
boolean explicit;
boolean omitDefault;
FramingContext() {
embed = true;
explicit = false;
omitDefault = false;
embeds = null;
}
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();
}
}
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.
*/
@SuppressWarnings("unchecked")
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(JsonLdConsts.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) : newMap()),
framed, null);
return framed;
}
/**
* Frames subjects according to the given frame.
*
* @param state the current framing state.
* @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.
*/
@SuppressWarnings("unchecked")
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, JsonLdConsts.EMBED, state.embed);
final Boolean explicitOn = getFrameFlag(frame, JsonLdConsts.EXPLICIT, state.explicit);
final Map flags = newMap();
flags.put(JsonLdConsts.EXPLICIT, explicitOn);
flags.put(JsonLdConsts.EMBED, embedOn);
// 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 = newMap();
output.put(JsonLdConsts.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(JsonLdConsts.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 (explicitOn && !frame.containsKey(prop)) {
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(JsonLdConsts.LIST)) {
// add empty list
final Map list = newMap();
list.put(JsonLdConsts.LIST, new ArrayList<>());
addFrameOutput(state, output, prop, list);
// add list objects
for (final Object listitem : (List) ((Map) item)
.get(JsonLdConsts.LIST)) {
// recurse into subject reference
if (JsonLdUtils.isNodeReference(listitem)) {
final Map tmp = newMap();
final String itemid = (String) ((Map) listitem)
.get(JsonLdConsts.ID);
// TODO: nodes may need to be node_map,
// which is global
tmp.put(itemid, this.nodeMap.get(itemid));
Map subframe;
if (frame.containsKey(prop)) {
subframe = (Map) ((List) frame
.get(prop)).get(0);
} else {
subframe = flags;
}
frame(state, tmp, subframe, list, JsonLdConsts.LIST);
} else {
// include other values automatically (TODO:
// may need JsonLdUtils.clone(n))
addFrameOutput(state, list, JsonLdConsts.LIST, listitem);
}
}
}
// recurse into subject reference
else if (JsonLdUtils.isNodeReference(item)) {
final Map tmp = newMap();
final String itemid = (String) ((Map) item)
.get(JsonLdConsts.ID);
// TODO: nodes may need to be node_map, which is
// global
tmp.put(itemid, this.nodeMap.get(itemid));
Map subframe;
if (frame.containsKey(prop)) {
subframe = (Map) ((List) frame.get(prop))
.get(0);
} else {
subframe = flags;
}
frame(state, tmp, subframe, 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 = newMap();
}
final boolean omitDefaultOn = getFrameFlag(propertyFrame,
JsonLdConsts.OMIT_DEFAULT, state.omitDefault);
if (!omitDefaultOn && !output.containsKey(prop)) {
Object def = "@null";
if (propertyFrame.containsKey(JsonLdConsts.DEFAULT)) {
def = JsonLdUtils.clone(propertyFrame.get(JsonLdConsts.DEFAULT));
}
if (!(def instanceof List)) {
final List tmp = new ArrayList<>();
tmp.add(def);
def = tmp;
}
final Map tmp1 = newMap(JsonLdConsts.PRESERVE, def);
final List tmp2 = new ArrayList<>();
tmp2.add(tmp1);
output.put(prop, tmp2);
}
}
// add output to parent
addFrameOutput(state, parent, property, output);
}
}
}
@SuppressWarnings("unchecked")
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(JsonLdConsts.VALUE)) {
value = ((Map) value).get(JsonLdConsts.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.
*/
@SuppressWarnings("unchecked")
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 = newMap(JsonLdConsts.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(JsonLdConsts.ID), id)) {
newvals.add(node);
} else {
newvals.add(v);
}
}
((Map) parent).put(property, newvals);
}
// recursively remove dependent dangling embeds
removeDependents(embeds, id);
}
@SuppressWarnings("unchecked")
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 : new HashSet<>(embeds.keySet())) {
final EmbedNode e = embeds.get(id_dep);
if (e == null || e.parent == null || !(e.parent instanceof Map)) {
continue;
}
final String pid = (String) ((Map) e.parent).get(JsonLdConsts.ID);
if (Obj.equals(id, pid)) {
embeds.remove(id_dep);
removeDependents(embeds, id_dep);
}
}
}
@SuppressWarnings("unchecked")
private Map filterNodes(FramingContext state, Map nodes,
Map frame) throws JsonLdError {
final Map rval = newMap();
for (final String id : nodes.keySet()) {
final Map element = (Map) nodes.get(id);
if (element != null && filterNode(state, element, frame)) {
rval.put(id, element);
}
}
return rval;
}
@SuppressWarnings("unchecked")
private boolean filterNode(FramingContext state, Map node,
Map frame) throws JsonLdError {
final Object types = frame.get(JsonLdConsts.TYPE);
if (types != null) {
if (!(types instanceof List)) {
throw new JsonLdError(Error.SYNTAX_ERROR, "frame @type must be an array");
}
Object nodeTypes = node.get(JsonLdConsts.TYPE);
if (nodeTypes == null) {
nodeTypes = new ArrayList<>();
} else if (!(nodeTypes instanceof List)) {
throw new JsonLdError(Error.SYNTAX_ERROR, "node @type must be an array");
}
if (((List) types).size() == 1 && ((List) types).get(0) instanceof Map
&& ((Map) ((List) types).get(0)).size() == 0) {
return !((List) nodeTypes).isEmpty();
} else {
for (final Object i : (List) nodeTypes) {
for (final Object j : (List) types) {
if (JsonLdUtils.deepCompare(i, j)) {
return true;
}
}
}
return false;
}
} else {
for (final String key : frame.keySet()) {
if (JsonLdConsts.ID.equals(key) || !isKeyword(key) && !(node.containsKey(key))) {
final Object frameObject = frame.get(key);
if (frameObject instanceof ArrayList) {
final ArrayList o = (ArrayList) frame.get(key);
boolean _default = false;
for (final Object oo : o) {
if (oo instanceof Map) {
if (((Map) oo).containsKey(JsonLdConsts.DEFAULT)) {
_default = true;
}
}
}
if (_default) {
continue;
}
}
return false;
}
}
return true;
}
}
/**
* Adds framing output to the given parent.
*
* @param state the current framing state.
* @param parent the parent to add to.
* @param property the parent property.
* @param output the output to add.
*/
@SuppressWarnings("unchecked")
private static void addFrameOutput(FramingContext state, Object parent, String property,
Object output) {
if (parent instanceof Map) {
List prop = (List) ((Map) parent).get(property);
if (prop == null) {
prop = new ArrayList<>();
((Map) parent).put(property, prop);
}
prop.add(output);
} else {
((List) parent).add(output);
}
}
/**
* Embeds values for the given subject and property into the given output
* during the framing algorithm.
*
* @param state the current framing state.
* @param element the subject.
* @param property the property.
* @param output the output.
*/
@SuppressWarnings("unchecked")
private void embedValues(FramingContext state, Map element, String property,
Object output) {
// embed subject properties in output
final List objects = (List) element.get(property);
for (Object o : objects) {
// handle subject reference
if (JsonLdUtils.isNodeReference(o)) {
final String sid = (String) ((Map) o).get(JsonLdConsts.ID);
// embed full subject if isn't already embedded
if (!state.embeds.containsKey(sid)) {
// add embed
final EmbedNode embed = new EmbedNode();
embed.parent = output;
embed.property = property;
state.embeds.put(sid, embed);
// recurse into subject
o = newMap();
Map s = (Map) this.nodeMap.get(sid);
if (s == null) {
s = newMap(JsonLdConsts.ID, sid);
}
for (final String prop : s.keySet()) {
// copy keywords
if (isKeyword(prop)) {
((Map) o).put(prop, JsonLdUtils.clone(s.get(prop)));
continue;
}
embedValues(state, s, prop, o);
}
}
addFrameOutput(state, output, property, o);
}
// copy non-subject value
else {
addFrameOutput(state, output, property, JsonLdUtils.clone(o));
}
}
}
/**
* Helper class for node usages.
*
* @author tristan
*/
private class UsagesNode {
UsagesNode(NodeMapNode node, String property, Map value) {
this.node = node;
this.property = property;
this.value = value;
}
public NodeMapNode node = null;
public String property = null;
public Map value = null;
}
@SuppressWarnings("unchecked")
private class NodeMapNode extends LinkedHashMap {
List usages = new ArrayList(4);
NodeMapNode(String id) {
super();
this.put(JsonLdConsts.ID, id);
}
// helper fucntion for 4.3.3
boolean isWellFormedListNode() {
if (usages.size() != 1) {
return false;
}
int keys = 0;
if (containsKey(RDF_FIRST)) {
keys++;
if (!(get(RDF_FIRST) instanceof List
&& ((List) get(RDF_FIRST)).size() == 1)) {
return false;
}
}
if (containsKey(RDF_REST)) {
keys++;
if (!(get(RDF_REST) instanceof List
&& ((List) get(RDF_REST)).size() == 1)) {
return false;
}
}
if (containsKey(JsonLdConsts.TYPE)) {
keys++;
if (!(get(JsonLdConsts.TYPE) instanceof List
&& ((List) get(JsonLdConsts.TYPE)).size() == 1)
&& RDF_LIST.equals(((List) get(JsonLdConsts.TYPE)).get(0))) {
return false;
}
}
// TODO: SPEC: 4.3.3 has no mention of @id
if (containsKey(JsonLdConsts.ID)) {
keys++;
}
return keys >= size();
}
// return this node without the usages variable
Map serialize() {
return new LinkedHashMap<>(this);
}
}
/**
* Converts RDF statements into JSON-LD.
*
* @param dataset the RDF statements.
* @return A list of JSON-LD objects found in the given dataset.
* @throws JsonLdError If there was an error during conversion from RDF to JSON-LD.
*/
List fromRDF(final RDFDataset dataset) throws JsonLdError {
return fromRDF(dataset, false);
}
/**
* Converts RDF statements into JSON-LD, presuming that there are no
* duplicates in the dataset.
*
* @param dataset the RDF statements.
* @param noDuplicatesInDataset True if there are no duplicates in the dataset and false
* otherwise.
* @return A list of JSON-LD objects found in the given dataset.
* @throws JsonLdError If there was an error during conversion from RDF to JSON-LD.
* Experimental method, only use if you are sure you need to use
* this method. Most users will need to use
* {@link #fromRDF(RDFDataset)}.
*/
@SuppressWarnings("unchecked")
List fromRDF(final RDFDataset dataset, boolean noDuplicatesInDataset)
throws JsonLdError {
// 1)
final Map defaultGraph = new LinkedHashMap<>(4);
// 2)
final Map> graphMap = new LinkedHashMap<>(
4);
graphMap.put(JsonLdConsts.DEFAULT, defaultGraph);
// 3/3.1)
for (final String name : dataset.graphNames()) {
final List graph = dataset.getQuads(name);
// 3.2+3.4)
Map nodeMap;
if (!graphMap.containsKey(name)) {
nodeMap = new LinkedHashMap<>();
graphMap.put(name, nodeMap);
} else {
nodeMap = graphMap.get(name);
}
// 3.3)
if (!JsonLdConsts.DEFAULT.equals(name) && !Obj.contains(defaultGraph, name)) {
defaultGraph.put(name, new NodeMapNode(name));
}
// 3.5)
for (final RDFDataset.Quad triple : graph) {
final String subject = triple.getSubject().getValue();
final String predicate = triple.getPredicate().getValue();
final RDFDataset.Node object = triple.getObject();
// 3.5.1+3.5.2)
NodeMapNode node;
if (!nodeMap.containsKey(subject)) {
node = new NodeMapNode(subject);
nodeMap.put(subject, node);
} else {
node = nodeMap.get(subject);
}
// 3.5.3)
if ((object.isIRI() || object.isBlankNode())
&& !nodeMap.containsKey(object.getValue())) {
nodeMap.put(object.getValue(), new NodeMapNode(object.getValue()));
}
// 3.5.4)
if (RDF_TYPE.equals(predicate) && (object.isIRI() || object.isBlankNode())
&& !opts.getUseRdfType()) {
JsonLdUtils.mergeValue(node, JsonLdConsts.TYPE, object.getValue());
continue;
}
// 3.5.5)
final Map value = object.toObject(opts.getUseNativeTypes());
// 3.5.6+7)
if (noDuplicatesInDataset) {
JsonLdUtils.laxMergeValue(node, predicate, value);
} else {
JsonLdUtils.mergeValue(node, predicate, value);
}
// 3.5.8)
if (object.isBlankNode() || object.isIRI()) {
// 3.5.8.1-3)
nodeMap.get(object.getValue()).usages
.add(new UsagesNode(node, predicate, value));
}
}
}
// 4)
for (final String name : graphMap.keySet()) {
final Map graph = graphMap.get(name);
// 4.1)
if (!graph.containsKey(RDF_NIL)) {
continue;
}
// 4.2)
final NodeMapNode nil = graph.get(RDF_NIL);
// 4.3)
for (final UsagesNode usage : nil.usages) {
// 4.3.1)
NodeMapNode node = usage.node;
String property = usage.property;
Map head = usage.value;
// 4.3.2)
final List list = new ArrayList<>(4);
final List listNodes = new ArrayList<>(4);
// 4.3.3)
while (RDF_REST.equals(property) && node.isWellFormedListNode()) {
// 4.3.3.1)
list.add(((List) node.get(RDF_FIRST)).get(0));
// 4.3.3.2)
listNodes.add((String) node.get(JsonLdConsts.ID));
// 4.3.3.3)
final UsagesNode nodeUsage = node.usages.get(0);
// 4.3.3.4)
node = nodeUsage.node;
property = nodeUsage.property;
head = nodeUsage.value;
// 4.3.3.5)
if (!JsonLdUtils.isBlankNode(node)) {
break;
}
}
// 4.3.4)
if (RDF_FIRST.equals(property)) {
// 4.3.4.1)
if (RDF_NIL.equals(node.get(JsonLdConsts.ID))) {
continue;
}
// 4.3.4.3)
final String headId = (String) head.get(JsonLdConsts.ID);
// 4.3.4.4-5)
head = (Map) ((List) graph.get(headId).get(RDF_REST))
.get(0);
// 4.3.4.6)
list.remove(list.size() - 1);
listNodes.remove(listNodes.size() - 1);
}
// 4.3.5)
head.remove(JsonLdConsts.ID);
// 4.3.6)
Collections.reverse(list);
// 4.3.7)
head.put(JsonLdConsts.LIST, list);
// 4.3.8)
for (final String nodeId : listNodes) {
graph.remove(nodeId);
}
}
}
// 5)
final List result = new ArrayList<>(4);
// 6)
final List ids = new ArrayList<>(defaultGraph.keySet());
Collections.sort(ids);
for (final String subject : ids) {
final NodeMapNode node = defaultGraph.get(subject);
// 6.1)
if (graphMap.containsKey(subject)) {
// 6.1.1)
node.put(JsonLdConsts.GRAPH, new ArrayList<>(4));
// 6.1.2)
final List