/core/src/main/java/com/github/jsonldjava/core/JSONLDProcessor.java
Java | 1862 lines | 1185 code | 174 blank | 503 comment | 395 complexity | 8af154bce8aac54ccf141b99f7c7597b MD5 | raw file
Possible License(s): BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- package com.github.jsonldjava.core;
- import static com.github.jsonldjava.core.JSONLDConsts.RDF_FIRST;
- 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.addValue;
- import static com.github.jsonldjava.core.JSONLDUtils.compactIri;
- import static com.github.jsonldjava.core.JSONLDUtils.compactValue;
- import static com.github.jsonldjava.core.JSONLDUtils.compareValues;
- import static com.github.jsonldjava.core.JSONLDUtils.createNodeMap;
- import static com.github.jsonldjava.core.JSONLDUtils.createTermDefinition;
- import static com.github.jsonldjava.core.JSONLDUtils.expandIri;
- import static com.github.jsonldjava.core.JSONLDUtils.expandLanguageMap;
- import static com.github.jsonldjava.core.JSONLDUtils.expandValue;
- import static com.github.jsonldjava.core.JSONLDUtils.hasValue;
- import static com.github.jsonldjava.core.JSONLDUtils.isAbsoluteIri;
- import static com.github.jsonldjava.core.JSONLDUtils.isArray;
- import static com.github.jsonldjava.core.JSONLDUtils.isKeyword;
- import static com.github.jsonldjava.core.JSONLDUtils.isList;
- import static com.github.jsonldjava.core.JSONLDUtils.isObject;
- import static com.github.jsonldjava.core.JSONLDUtils.isString;
- import static com.github.jsonldjava.core.JSONLDUtils.isSubjectReference;
- import static com.github.jsonldjava.core.JSONLDUtils.isValue;
- import static com.github.jsonldjava.core.JSONLDUtils.removeValue;
- import static com.github.jsonldjava.core.JSONLDUtils.validateTypeValue;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.github.jsonldjava.utils.JSONUtils;
- import com.github.jsonldjava.utils.Obj;
- import com.github.jsonldjava.utils.URL;
- public class JSONLDProcessor {
- private static final Logger LOG = LoggerFactory.getLogger(JSONLDProcessor.class);
- Options opts;
- public JSONLDProcessor() {
- opts = new Options("");
- }
- public JSONLDProcessor(Options opts) {
- if (opts == null) {
- opts = new Options("");
- } else {
- this.opts = opts;
- }
- }
- /**
- * Processes a local context and returns a new active context.
- *
- * @param activeCtx
- * the current active context.
- * @param localCtx
- * the local context to process.
- * @param options
- * the context processing options.
- *
- * @return the new active context.
- */
- ActiveContext processContext(ActiveContext activeCtx, Object localCtx)
- throws JSONLDProcessingError {
- // TODO: get context from cache if available
- // initialize the resulting context
- ActiveContext rval = activeCtx.clone();
- // normalize local context to an array of @context objects
- if (localCtx instanceof Map && ((Map) localCtx).containsKey("@context")
- && ((Map) localCtx).get("@context") instanceof List) {
- localCtx = ((Map) localCtx).get("@context");
- }
- List<Map<String, Object>> ctxs;
- if (localCtx instanceof List) {
- ctxs = (List<Map<String, Object>>) localCtx;
- } else {
- ctxs = new ArrayList<Map<String, Object>>();
- ctxs.add((Map<String, Object>) localCtx);
- }
- // process each context in order
- for (Object ctx : ctxs) {
- if (ctx == null) {
- // reset to initial context
- rval = new ActiveContext(opts);
- continue;
- }
- // context must be an object by now, all URLs resolved before this
- // call
- if (ctx instanceof Map) {
- // dereference @context key if present
- if (((Map<String, Object>) ctx).containsKey("@context")) {
- ctx = ((Map<String, Object>) ctx).get("@context");
- }
- } else {
- // context must be an object by now, all URLs resolved before
- // this call
- throw new JSONLDProcessingError("@context must be an object").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("context", ctx);
- }
- // define context mappings for keys in local context
- final Map<String, Boolean> defined = new LinkedHashMap<String, Boolean>();
- // helper for access to ctx as a map
- final Map<String, Object> ctxm = (Map<String, Object>) ctx;
- // handle @base
- if (ctxm.containsKey("@base")) {
- Object base = ctxm.get("@base");
- // reset base
- if (base == null) {
- base = opts.base;
- } else if (!isString(base)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; the value of \"@base\" in a "
- + "@context must be a string or null.").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("context", ctx);
- } else if (!"".equals(base) && !isAbsoluteIri((String) base)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; the value of \"@base\" in a "
- + "@context must be an absolute IRI or the empty string.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("context",
- ctx);
- }
- base = URL.parse((String) base);
- rval.put("@base", base);
- defined.put("@base", true);
- }
- // handle @vocab
- if (ctxm.containsKey("@vocab")) {
- final Object value = ctxm.get("@vocab");
- if (value == null) {
- rval.remove("@vocab");
- } else if (!isString(value)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; the value of \"@vocab\" in a "
- + "@context must be a string or null.").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("context", ctx);
- } else if (!isAbsoluteIri((String) value)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; the value of \"@vocab\" in a "
- + "@context must be an absolute IRI.").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("context", ctx);
- } else {
- rval.put("@vocab", value);
- }
- defined.put("@vocab", true);
- }
- // handle @language
- if (ctxm.containsKey("@language")) {
- final Object value = ctxm.get("@language");
- if (value == null) {
- rval.remove("@language");
- } else if (!isString(value)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; the value of \"@language\" in a "
- + "@context must be a string or null.").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("context", ctx);
- } else {
- rval.put("@language", ((String) value).toLowerCase());
- }
- defined.put("@language", true);
- }
- // process all other keys
- for (final String key : ctxm.keySet()) {
- createTermDefinition(rval, ctxm, key, defined);
- }
- }
- // TODO: cache results
- return rval;
- }
- /**
- * Recursively expands an element using the given context. Any context in
- * the element will be removed. All context URLs must have been retrieved
- * before calling this method.
- *
- * @param activeCtx
- * the context to use.
- * @param activeProperty
- * the property for the element, null for none.
- * @param element
- * the element to expand.
- * @param options
- * the expansion options.
- * @param insideList
- * true if the element is a list, false if not.
- *
- * @return the expanded value.
- *
- * TODO: - does this function always return a map, or can it also
- * return a list, the expandedValue variable below seems to assume a
- * map, but in javascript, `in` will just return false if the result
- * is a list
- */
- public Object expand(ActiveContext activeCtx, String activeProperty, Object element,
- Boolean insideList) throws JSONLDProcessingError {
- // nothing to expand
- if (element == null) {
- return null;
- }
- // recursively expand array
- if (element instanceof List) {
- final List<Object> rval = new ArrayList<Object>();
- for (final Object i : (List<Object>) element) {
- // expand element
- final Object e = expand(activeCtx, activeProperty, i, insideList);
- if (insideList && (isArray(e) || isList(e))) {
- // lists of lists are illegal
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; lists of lists are not permitted.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR);
- // drop null values
- } else if (e != null) {
- if (isArray(e)) {
- rval.addAll((Collection<? extends Object>) e);
- } else {
- rval.add(e);
- }
- }
- }
- return rval;
- }
- // recursively expand object
- if (isObject(element)) {
- // access helper
- final Map<String, Object> elem = (Map<String, Object>) element;
- // if element has a context, process it
- if (elem.containsKey("@context")) {
- activeCtx = processContext(activeCtx, elem.get("@context"));
- // elem.remove("@context");
- }
- // expand the active property
- final String expandedActiveProperty = expandIri(activeCtx, activeProperty, false, true,
- null, null); // {vocab: true}
- Object rval = new LinkedHashMap<String, Object>();
- Map<String, Object> mval = (Map<String, Object>) rval; // to make
- // things
- // easier
- // while we
- // know rval
- // is a map
- final List<String> keys = new ArrayList<String>(elem.keySet());
- Collections.sort(keys);
- for (final String key : keys) {
- final Object value = elem.get(key);
- Object expandedValue;
- // skip @context
- if (key.equals("@context")) {
- continue;
- }
- // expand key to IRI
- final String expandedProperty = expandIri(activeCtx, key, false, true, null, null); // {vocab:
- // true}
- // drop non-absolute IRI keys that aren't keywords
- if (expandedProperty == null
- || !(isAbsoluteIri(expandedProperty) || isKeyword(expandedProperty))) {
- continue;
- }
- if (isKeyword(expandedProperty) && "@reverse".equals(expandedActiveProperty)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; a keyword cannot be used as a @reverse propery.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("value",
- value);
- }
- if ("@id".equals(expandedProperty) && !isString(value)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@id\" value must a string.").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("value", value);
- }
- // validate @type value
- if ("@type".equals(expandedProperty)) {
- validateTypeValue(value);
- }
- // @graph must be an array or an object
- if ("@graph".equals(expandedProperty) && !(isObject(value) || isArray(value))) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@graph\" value must be an object or an array.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("value",
- value);
- }
- // @value must not be an object or an array
- if ("@value".equals(expandedProperty)
- && (value instanceof Map || value instanceof List)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@value\" value must not be an object or an array.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("value",
- value);
- }
- // @language must be a string
- if ("@language".equals(expandedProperty) && !(value instanceof String)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@language\" value must be a string.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("value",
- value);
- }
- // @index must be a string
- if ("@index".equals(expandedProperty) && !(value instanceof String)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@index\" value must be a string.").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("value", value);
- }
- // @reverse must be an object
- if ("@reverse".equals(expandedProperty)) {
- if (!isObject(value)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@reverse\" value must be an object.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail(
- "value", value);
- }
- expandedValue = expand(activeCtx, "@reverse", value, insideList);
- // properties double-reversed
- if (expandedValue instanceof Map
- && ((Map<String, Object>) expandedValue).containsKey("@reverse")) {
- // TODO: javascript seems to assume that the value of
- // reverse will always be an object, may need to add a
- // check here if this turns out to be the case
- final Map<String, Object> rev = (Map<String, Object>) ((Map<String, Object>) expandedValue)
- .get("@reverse");
- for (final String property : rev.keySet()) {
- addValue(mval, property, rev.get(property), true);
- }
- }
- // FIXME: can this be merged with the code below to
- // simplify?
- // merge in all reversed properties
- if (expandedValue instanceof Map) { // TODO: javascript
- // doesn't make this
- // check, can we assume
- // expandedValue is
- // always going to be an
- // object?
- Map<String, Object> reverseMap = (Map<String, Object>) mval.get("@reverse");
- for (final String property : ((Map<String, Object>) expandedValue).keySet()) {
- if ("@reverse".equals(property)) {
- continue;
- }
- if (reverseMap == null) {
- reverseMap = new LinkedHashMap<String, Object>();
- mval.put("@reverse", reverseMap);
- }
- addValue(reverseMap, property, new ArrayList<Object>(), true);
- final List<Object> items = (List<Object>) ((Map<String, Object>) expandedValue)
- .get(property);
- for (final Object item : items) {
- if (isValue(item) || isList(item)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@reverse\" value must not be a @value or an @list.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR)
- .setDetail("value", expandedValue);
- }
- addValue(reverseMap, property, item, true);
- }
- }
- }
- continue;
- }
- final String container = (String) activeCtx.getContextValue(key, "@container");
- // handle language map container (skip if value is not an
- // object)
- if ("@language".equals(container) && isObject(value)) {
- expandedValue = expandLanguageMap((Map<String, Object>) value);
- }
- // handle index container (skip if value is not an object)
- else if ("@index".equals(container) && isObject(value)) {
- // NOTE: implementing embeded function expandIndexMap from
- // javascript as rolled out code here
- // as it doesn't call itself and needs access to this
- // instance's expand method.
- // using eim_ prefix for variables to avoid clashes
- final String eim_activeProperty = key;
- final List<Object> eim_rval = new ArrayList<Object>();
- for (final String eim_key : ((Map<String, Object>) value).keySet()) {
- List<Object> eim_val;
- if (!isArray(((Map<String, Object>) value).get(eim_key))) {
- eim_val = new ArrayList<Object>();
- eim_val.add(((Map<String, Object>) value).get(eim_key));
- } else {
- eim_val = (List<Object>) ((Map<String, Object>) value).get(eim_key);
- }
- // NOTE: javascript assumes list result here, so I am as
- // well
- eim_val = (List<Object>) expand(activeCtx, eim_activeProperty, eim_val,
- false);
- for (final Object eim_item : eim_val) {
- if (isObject(eim_item)) {
- if (!((Map<String, Object>) eim_item).containsKey("@index")) {
- ((Map<String, Object>) eim_item).put("@index", eim_key);
- }
- eim_rval.add(eim_item);
- }
- }
- }
- expandedValue = eim_rval;
- } else {
- // recurse into @list or @set
- final Boolean isList = "@list".equals(expandedProperty);
- if (isList || "@set".equals(expandedProperty)) {
- String nextActiveProperty = activeProperty;
- if (isList && "@graph".equals(expandedActiveProperty)) {
- nextActiveProperty = null;
- }
- expandedValue = expand(activeCtx, nextActiveProperty, value, isList);
- if (isList && isList(expandedValue)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; lists of lists are not permitted.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR);
- }
- } else {
- // recursively expand value with key as new active
- // property
- expandedValue = expand(activeCtx, key, value, false);
- }
- }
- // drop null values if property is not @value
- if (expandedValue == null && !"@value".equals(expandedProperty)) {
- continue;
- }
- // convert expanded value to @list if container specified it
- if (!"@list".equals(expandedProperty) && !isList(expandedValue)
- && "@list".equals(container)) {
- // ensure expanded value is an array
- final Map<String, Object> tm = new LinkedHashMap<String, Object>();
- List<Object> tl;
- if (isArray(expandedValue)) {
- tl = (List<Object>) expandedValue;
- } else {
- tl = new ArrayList<Object>();
- tl.add(expandedValue);
- }
- tm.put("@list", tl);
- expandedValue = tm;
- }
- // FIXME: can this be merged with the code above to simplify?
- // merge in all reversed properties
- if (Boolean.TRUE.equals(Obj.get(activeCtx.mappings, key, "reverse"))) {
- final Map<String, Object> reverseMap = new LinkedHashMap<String, Object>();
- mval.put("@reverse", reverseMap);
- if (!isArray(expandedValue)) {
- final List<Object> tmp = new ArrayList<Object>();
- tmp.add(expandedValue);
- expandedValue = tmp;
- }
- for (final Object item : (List<Object>) expandedValue) {
- if (isValue(item) || isList(item)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; \"@reverse\" value must not be a @value or an @list.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail(
- "value", expandedValue);
- }
- addValue(reverseMap, expandedProperty, item, true);
- }
- continue;
- }
- // add value for property
- // use an array except for certain keywords
- final Boolean useArray = !("@index".equals(expandedProperty)
- || "@id".equals(expandedProperty) || "@type".equals(expandedProperty)
- || "@value".equals(expandedProperty) || "@language"
- .equals(expandedProperty));
- addValue(mval, expandedProperty, expandedValue, useArray);
- }
- // get property count on expanded output
- int count = mval.size();
- // @value must only have @language or @type
- if (mval.containsKey("@value")) {
- // @value must only have @language or @type
- if (mval.containsKey("@type") && mval.containsKey("@language")) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; an element containing \"@value\" may not contain both \"@type\" and \"@language\".")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("element",
- mval);
- }
- int validCount = count - 1;
- if (mval.containsKey("@type") || mval.containsKey("@language")) {
- validCount -= 1;
- }
- if (mval.containsKey("@index")) {
- validCount -= 1;
- }
- if (validCount != 0) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; an element containing \"@value\" may only have an \"@index\" property "
- + "and at most one other property which can be \"@type\" or \"@language\".")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("element",
- mval);
- }
- // drop null @values
- if (mval.get("@value") == null) {
- rval = null;
- mval = null;
- }
- // drop @language if @value isn't a string
- else if (mval.containsKey("@language") && !isString(mval.get("@value"))) {
- mval.remove("@language");
- }
- }
- // convert @type to an array
- else if (mval.containsKey("@type") && !isArray(mval.get("@type"))) {
- final List<Object> tmp = new ArrayList<Object>();
- tmp.add(mval.get("@type"));
- mval.put("@type", tmp);
- }
- // handle @set and @list
- else if (mval.containsKey("@set") || mval.containsKey("@list")) {
- if (count > 1 && (count != 2 && mval.containsKey("@index"))) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD syntax; if an element has the property \"@set\" or \"@list\", then it can have "
- + "at most one other property that is \"@index\".").setType(
- JSONLDProcessingError.Error.SYNTAX_ERROR).setDetail("element", mval);
- }
- // optimize away @set
- if (mval.containsKey("@set")) {
- rval = mval.get("@set");
- mval = null; // result is no longer a map, so don't allow
- // this to be used anymore
- count = ((Collection) rval).size(); // TODO: i'm sure the
- // result here should be
- // a List, but
- // Collection works, so
- // it'll do for now
- }
- }
- // drop objects with only @language
- else if (mval.containsKey("@language") && count == 1) {
- rval = null;
- mval = null;
- }
- // drop certain top-level object that do not occur in lists
- if (isObject(rval) && !opts.keepFreeFloatingNodes && !insideList
- && (activeProperty == null || "@graph".equals(expandedActiveProperty))) {
- // drop empty object or top-level @value
- if (count == 0 || mval.containsKey("@value")) {
- rval = null;
- mval = null;
- } else {
- // drop nodes that generate no triples
- boolean hasTriples = false;
- for (final String key : mval.keySet()) {
- if (hasTriples) {
- break;
- }
- if (!isKeyword(key) || "@graph".equals(key) || "@type".equals(key)) {
- hasTriples = true;
- }
- }
- if (!hasTriples) {
- rval = null;
- mval = null;
- }
- }
- }
- return rval;
- }
- // drop top-level scalars that are not in lists
- if (!insideList
- && (activeProperty == null || "@graph".equals(expandIri(activeCtx, activeProperty,
- false, true, null, null)))) {
- return null;
- }
- // expand element according to value expansion rules
- return expandValue(activeCtx, activeProperty, element);
- }
- /**
- * Recursively compacts an element using the given active context. All
- * values must be in expanded form before this method is called.
- *
- * @param activeCtx
- * the active context to use.
- * @param activeProperty
- * the compacted property associated with the element to compact,
- * null for none.
- * @param element
- * the element to compact.
- * @param options
- * the compaction options.
- *
- * @return the compacted value.
- */
- public Object compact(ActiveContext activeCtx, String activeProperty, Object element)
- throws JSONLDProcessingError {
- // recursively compact array
- if (isArray(element)) {
- final List<Object> rval = new ArrayList<Object>();
- for (final Object i : (List<Object>) element) {
- // compact, dropping any null values
- final Object compacted = compact(activeCtx, activeProperty, i);
- if (compacted != null) {
- rval.add(compacted);
- }
- }
- if (opts.compactArrays && rval.size() == 1) {
- // use single element if no container is specified
- final Object container = activeCtx.getContextValue(activeProperty, "@container");
- if (container == null) {
- return rval.get(0);
- }
- }
- return rval;
- }
- // recursively compact object
- if (isObject(element)) {
- // access helper
- final Map<String, Object> elem = (Map<String, Object>) element;
- // do value compaction on @value and subject references
- if (isValue(element) || isSubjectReference(element)) {
- return compactValue(activeCtx, activeProperty, element);
- }
- // FIXME: avoid misuse of active property as an expanded property?
- final boolean insideReverse = ("@reverse".equals(activeProperty));
- // process element keys in order
- final List<String> keys = new ArrayList<String>(elem.keySet());
- Collections.sort(keys);
- final Map<String, Object> rval = new LinkedHashMap<String, Object>();
- for (final String expandedProperty : keys) {
- final Object expandedValue = elem.get(expandedProperty);
- /*
- * TODO: // handle ignored keys if (opts.isIgnored(key)) {
- * //JSONLDUtils.addValue(rval, key, value, false);
- * rval.put(key, value); continue; }
- */
- // compact @id and @type(s)
- if ("@id".equals(expandedProperty) || "@type".equals(expandedProperty)) {
- Object compactedValue;
- // compact single @id
- if (isString(expandedValue)) {
- compactedValue = compactIri(activeCtx, (String) expandedValue, null,
- "@type".equals(expandedProperty), false);
- }
- // expanded value must be a @type array
- else {
- final List<String> types = new ArrayList<String>();
- for (final String i : (List<String>) expandedValue) {
- types.add(compactIri(activeCtx, i, null, true, false));
- }
- compactedValue = types;
- }
- // use keyword alias and add value
- final String alias = compactIri(activeCtx, expandedProperty);
- addValue(rval, alias, compactedValue, isArray(compactedValue)
- && ((List<Object>) expandedValue).size() == 0);
- continue;
- }
- // handle @reverse
- if ("@reverse".equals(expandedProperty)) {
- // recursively compact expanded value
- // TODO: i'm assuming this will always be a map due to the
- // rest of the code
- final Map<String, Object> compactedValue = (Map<String, Object>) compact(
- activeCtx, "@reverse", expandedValue);
- // handle double-reversed properties
- for (final String compactedProperty : compactedValue.keySet()) {
- if (Boolean.TRUE.equals(Obj.get(activeCtx.mappings, compactedProperty,
- "reverse"))) {
- if (!rval.containsKey(compactedProperty) && !opts.compactArrays) {
- rval.put(compactedProperty, new ArrayList<Object>());
- }
- addValue(rval, compactedProperty, compactedValue.get(compactedProperty));
- compactedValue.remove(compactedProperty);
- }
- }
- if (compactedValue.size() > 0) {
- // use keyword alias and add value
- addValue(rval, compactIri(activeCtx, expandedProperty), compactedValue);
- }
- continue;
- }
- // handle @index property
- if ("@index".equals(expandedProperty)) {
- // drop @index if inside an @index container
- final String container = (String) activeCtx.getContextValue(activeProperty,
- "@container");
- if ("@index".equals(container)) {
- continue;
- }
- // use keyword alias and add value
- addValue(rval, compactIri(activeCtx, expandedProperty), expandedValue);
- continue;
- }
- // NOTE: expanded value must be an array due to expansion
- // algorithm.
- // preserve empty arrays
- if (((List<Object>) expandedValue).size() == 0) {
- addValue(
- rval,
- compactIri(activeCtx, expandedProperty, expandedValue, true,
- insideReverse), expandedValue, true);
- }
- // recusively process array values
- for (final Object expandedItem : (List<Object>) expandedValue) {
- // compact property and get container type
- final String itemActiveProperty = compactIri(activeCtx, expandedProperty,
- expandedItem, true, insideReverse);
- final String container = (String) activeCtx.getContextValue(itemActiveProperty,
- "@container");
- // get @list value if appropriate
- final boolean isList = isList(expandedItem);
- Object list = null;
- if (isList) {
- list = ((Map<String, Object>) expandedItem).get("@list");
- }
- // recursively compact expanded item
- Object compactedItem = compact(activeCtx, itemActiveProperty, isList ? list
- : expandedItem);
- // handle @list
- if (isList) {
- // ensure @list value is an array
- if (!isArray(compactedItem)) {
- final List<Object> tmp = new ArrayList<Object>();
- tmp.add(compactedItem);
- compactedItem = tmp;
- }
- if (!"@list".equals(container)) {
- // wrap using @list alias
- final Map<String, Object> wrapper = new LinkedHashMap<String, Object>();
- wrapper.put(compactIri(activeCtx, "@list"), compactedItem);
- compactedItem = wrapper;
- // include @index from expanded @list, if any
- if (((Map<String, Object>) expandedItem).containsKey("@index")) {
- ((Map<String, Object>) compactedItem).put(
- compactIri(activeCtx, "@index"),
- ((Map<String, Object>) expandedItem).get("@index"));
- }
- }
- // can't use @list container for more than 1 list
- else if (rval.containsKey(itemActiveProperty)) {
- throw new JSONLDProcessingError(
- "Invalid JSON-LD compact error; property has a \"@list\" @container "
- + "rule but there is more than a single @list that matches "
- + "the compacted term in the document. Compaction might mix "
- + "unwanted items into the list.")
- .setType(JSONLDProcessingError.Error.SYNTAX_ERROR);
- }
- }
- // handle language and index maps
- if ("@language".equals(container) || "@index".equals(container)) {
- // get or create the map object
- Map<String, Object> mapObject;
- if (rval.containsKey(itemActiveProperty)) {
- mapObject = (Map<String, Object>) rval.get(itemActiveProperty);
- } else {
- mapObject = new LinkedHashMap<String, Object>();
- rval.put(itemActiveProperty, mapObject);
- }
- // if container is a language map, simplify compacted
- // value to
- // a simple string
- if ("@language".equals(container) && isValue(compactedItem)) {
- compactedItem = ((Map<String, Object>) compactedItem).get("@value");
- }
- // add compact value to map object using key from
- // expanded value
- // based on the container type
- addValue(mapObject,
- (String) ((Map<String, Object>) expandedItem).get(container),
- compactedItem);
- } else {
- // use an array if: compactArrays flag is false,
- // @container is @set or @list, value is an empty
- // array, or key is @graph
- final Boolean isArray = (!opts.compactArrays
- || "@set".equals(container)
- || "@list".equals(container)
- || (isArray(compactedItem) && ((List<Object>) compactedItem).size() == 0)
- || "@list".equals(expandedProperty) || "@graph"
- .equals(expandedProperty));
- // add compact value
- addValue(rval, itemActiveProperty, compactedItem, isArray);
- }
- }
- }
- return rval;
- }
- // only primatives remain which are already compact
- return element;
- }
- private class FramingContext {
- public Map<String, Object> embeds = null;
- public Map<String, Object> graphs = null;
- public Map<String, Object> subjects = null;
- public Options options = opts;
- }
- /**
- * Performs JSON-LD framing.
- *
- * @param input
- * the expanded JSON-LD to frame.
- * @param frame
- * the expanded JSON-LD frame to use.
- * @param options
- * the framing options.
- *
- * @return the framed output.
- * @throws JSONLDProcessingError
- */
- public Object frame(Object input, Object frame) throws JSONLDProcessingError {
- // create framing state
- final FramingContext state = new FramingContext();
- // Map<String,Object> state = new HashMap<String, Object>();
- // state.put("options", this.opts);
- state.graphs = new LinkedHashMap<String, Object>();
- state.graphs.put("@default", new LinkedHashMap<String, Object>());
- state.graphs.put("@merged", new LinkedHashMap<String, Object>());
- // produce a map of all graphs and name each bnode
- // FIXME: currently uses subjects from @merged graph only
- final UniqueNamer namer = new UniqueNamer("_:b");
- createNodeMap(input, state.graphs, "@merged", namer);
- state.subjects = (Map<String, Object>) state.graphs.get("@merged");
- // frame the subjects
- final List<Object> framed = new ArrayList<Object>();
- final List<String> sortedKeys = new ArrayList<String>(state.subjects.keySet());
- Collections.sort(sortedKeys);
- frame(state, sortedKeys, frame, 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 JSONLDProcessingError
- */
- private void frame(FramingContext state, Collection<String> subjects, Object frame,
- Object parent, String property) throws JSONLDProcessingError {
- // validate the frame
- validateFrame(state, frame);
- // NOTE: once validated we move to the function where the frame is
- // specifically a map
- frame(state, subjects, (Map<String, Object>) ((List<Object>) frame).get(0), parent,
- property);
- }
- private void frame(FramingContext state, Collection<String> subjects,
- Map<String, Object> frame, Object parent, String property) throws JSONLDProcessingError {
- // filter out subjects that match the frame
- final Map<String, Object> matches = filterSubjects(state, subjects, frame);
- // get flags for current frame
- final Options options = state.options;
- Boolean embedOn = (frame.containsKey("@embed")) ? (Boolean) ((List) frame.get("@embed"))
- .get(0) : options.embed;
- final Boolean explicicOn = (frame.containsKey("@explicit")) ? (Boolean) ((List) frame
- .get("@explicit")).get(0) : options.explicit;
- // add matches to output
- final List<String> ids = new ArrayList<String>(matches.keySet());
- Collections.sort(ids);
- for (final String id : ids) {
- // Note: In order to treat each top-level match as a
- // compartmentalized
- // result, create an independent copy of the embedded subjects map
- // when the
- // property is null, which only occurs at the top-level.
- if (property == null) {
- state.embeds = new LinkedHashMap<String, Object>();
- }
- // start output
- final Map<String, Object> output = new LinkedHashMap<String, Object>();
- output.put("@id", id);
- // prepare embed meta info
- final Map<String, Object> embed = new LinkedHashMap<String, Object>();
- embed.put("parent", parent);
- embed.put("property", property);
- // if embed is on and there is an existing embed
- if (embedOn && state.embeds.containsKey(id)) {
- // only overwrite an existing embed if it has already been added
- // to its
- // parent -- otherwise its parent is somewhere up the tree from
- // this
- // embed and the embed would occur twice once the tree is added
- embedOn = false;
- // existing embed's parent is an array
- final Map<String, Object> existing = (Map<String, Object>) state.embeds.get(id);
- if (isArray(existing.get("parent"))) {
- for (final Object o : (List<Object>) existing.get("parent")) {
- if (compareValues(output, o)) {
- embedOn = true;
- break;
- }
- }
- }
- // existing embed's parent is an object
- else if (hasValue((Map<String, Object>) existing.get("parent"),
- (String) existing.get("property"), output)) {
- embedOn = true;
- }
- // 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, embed);
- // iterate over subject properties
- final Map<String, Object> subject = (Map<String, Object>) matches.get(id);
- List<String> props = new ArrayList<String>(subject.keySet());
- Collections.sort(props);
- for (final String prop : props) {
- // handle ignored keys
- if (opts.isIgnored(prop)) {
- output.put(prop, JSONLDUtils.clone(subject.get(prop)));
- continue;
- }
- // copy keywords to output
- if (isKeyword(prop)) {
- output.put(prop, JSONLDUtils.clone(subject.get(prop)));
- continue;
- }
- // if property isn't in the frame
- if (!frame.containsKey(prop)) {
- // if explicit is off, embed values
- if (!explicicOn) {
- embedValues(state, subject, prop, output);
- }
- continue;
- }
- // add objects
- final Object objects = subject.get(prop);
- // TODO: i've done some crazy stuff here because i'm unsure
- // if objects is always a list or if it can
- // be a map as well. I think it's always a map, but i'll get
- // it working like this first
- for (final Object i : objects instanceof List ? (List) objects
- : ((Map) objects).keySet()) {
- final Object o = objects instanceof List ? i : ((Map) objects).get(i);
- // recurse into list
- if (isList(o)) {
- // add empty list
- final Map<String, Object> list = new LinkedHashMap<String, Object>();
- list.put("@list", new ArrayList<Object>());
- addFrameOutput(state, output, prop, list);
- // add list objects
- final List src = (List) ((Map) o).get("@list");
- for (final Object n : src) {
- // recurse into subject reference
- if (isSubjectReference(o)) {
- final List tmp = new ArrayList();
- tmp.add(((Map) n).get("@id"));
- frame(state, tmp, frame.get(prop), list, "@list");
- } else {
- …
Large files files are truncated, but you can click here to view the full file