package com.github.jsonldjava.core; import static com.github.jsonldjava.core.JsonLdUtils.compareShortestLeast; import static com.github.jsonldjava.utils.Obj.newMap; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.github.jsonldjava.core.JsonLdError.Error; import com.github.jsonldjava.utils.JsonLdUrl; import com.github.jsonldjava.utils.Obj; /** * A helper class which still stores all the values in a map but gives member * variables easily access certain keys * * @author tristan * */ public class Context extends LinkedHashMap<String, Object> { private JsonLdOptions options; private Map<String, Object> termDefinitions; public Map<String, Object> inverse = null; public Context() { this(new JsonLdOptions()); } public Context(JsonLdOptions opts) { super(); init(opts); } public Context(Map<String, Object> map, JsonLdOptions opts) { super(map); init(opts); } public Context(Map<String, Object> map) { super(map); init(new JsonLdOptions()); } public Context(Object context, JsonLdOptions opts) { // TODO: load remote context super(context instanceof Map ? (Map<String, Object>) context : null); init(opts); } private void init(JsonLdOptions options) { this.options = options; if (options.getBase() != null) { this.put("@base", options.getBase()); } this.termDefinitions = newMap(); } /** * Value Compaction Algorithm * * http://json-ld.org/spec/latest/json-ld-api/#value-compaction * * @param activeProperty * The Active Property * @param value * The value to compact * @return The compacted value */ public Object compactValue(String activeProperty, Map<String, Object> value) { // 1) int numberMembers = value.size(); // 2) if (value.containsKey("@index") && "@index".equals(this.getContainer(activeProperty))) { numberMembers--; } // 3) if (numberMembers > 2) { return value; } // 4) final String typeMapping = getTypeMapping(activeProperty); final String languageMapping = getLanguageMapping(activeProperty); if (value.containsKey("@id")) { // 4.1) if (numberMembers == 1 && "@id".equals(typeMapping)) { return compactIri((String) value.get("@id")); } // 4.2) if (numberMembers == 1 && "@vocab".equals(typeMapping)) { return compactIri((String) value.get("@id"), true); } // 4.3) return value; } final Object valueValue = value.get("@value"); // 5) if (value.containsKey("@type") && Obj.equals(value.get("@type"), typeMapping)) { return valueValue; } // 6) if (value.containsKey("@language")) { // TODO: SPEC: doesn't specify to check default language as well if (Obj.equals(value.get("@language"), languageMapping) || Obj.equals(value.get("@language"), this.get("@language"))) { return valueValue; } } // 7) if (numberMembers == 1 && (!(valueValue instanceof String) || !this.containsKey("@language") || (termDefinitions .containsKey(activeProperty) && getTermDefinition(activeProperty).containsKey("@language") && languageMapping == null))) { return valueValue; } // 8) return value; } /** * Context Processing Algorithm * * http://json-ld.org/spec/latest/json-ld-api/#context-processing-algorithms * * @param localContext * The Local Context object. * @param remoteContexts * The list of Strings denoting the remote Context URLs. * @return The parsed and merged Context. * @throws JsonLdError * If there is an error parsing the contexts. */ public Context parse(Object localContext, List<String> remoteContexts) throws JsonLdError { if (remoteContexts == null) { remoteContexts = new ArrayList<>(); } // 1. Initialize result to the result of cloning active context. Context result = this.clone(); // TODO: clone? // 2) if (!(localContext instanceof List)) { final Object temp = localContext; localContext = new ArrayList<>(); ((List<Object>) localContext).add(temp); } // 3) for (Object context : ((List<Object>) localContext)) { // 3.1) if (context == null) { result = new Context(this.options); continue; } else if (context instanceof Context) { result = ((Context) context).clone(); } // 3.2) else if (context instanceof String) { String uri = (String) result.get("@base"); uri = JsonLdUrl.resolve(uri, (String) context); // 3.2.2 if (remoteContexts.contains(uri)) { throw new JsonLdError(Error.RECURSIVE_CONTEXT_INCLUSION, uri); } remoteContexts.add(uri); // 3.2.3: Dereference context final RemoteDocument rd = this.options.getDocumentLoader().loadDocument(uri); final Object remoteContext = rd.document; if (!(remoteContext instanceof Map) || !((Map<String, Object>) remoteContext).containsKey("@context")) { // If the dereferenced document has no top-level JSON object // with an @context member throw new JsonLdError(Error.INVALID_REMOTE_CONTEXT, context); } context = ((Map<String, Object>) remoteContext).get("@context"); // 3.2.4 result = result.parse(context, remoteContexts); // 3.2.5 continue; } else if (!(context instanceof Map)) { // 3.3 throw new JsonLdError(Error.INVALID_LOCAL_CONTEXT, context); } // 3.4 if (remoteContexts.isEmpty() && ((Map<String, Object>) context).containsKey("@base")) { final Object value = ((Map<String, Object>) context).get("@base"); if (value == null) { result.remove("@base"); } else if (value instanceof String) { if (JsonLdUtils.isAbsoluteIri((String) value)) { result.put("@base", value); } else { final String baseUri = (String) result.get("@base"); if (!JsonLdUtils.isAbsoluteIri(baseUri)) { throw new JsonLdError(Error.INVALID_BASE_IRI, baseUri); } result.put("@base", JsonLdUrl.resolve(baseUri, (String) value)); } } else { throw new JsonLdError(JsonLdError.Error.INVALID_BASE_IRI, "@base must be a string"); } } // 3.5 if (((Map<String, Object>) context).containsKey("@vocab")) { final Object value = ((Map<String, Object>) context).get("@vocab"); if (value == null) { result.remove("@vocab"); } else if (value instanceof String) { if (JsonLdUtils.isAbsoluteIri((String) value)) { result.put("@vocab", value); } else { throw new JsonLdError(Error.INVALID_VOCAB_MAPPING, "@value must be an absolute IRI"); } } else { throw new JsonLdError(Error.INVALID_VOCAB_MAPPING, "@vocab must be a string or null"); } } // 3.6 if (((Map<String, Object>) context).containsKey("@language")) { final Object value = ((Map<String, Object>) context).get("@language"); if (value == null) { result.remove("@language"); } else if (value instanceof String) { result.put("@language", ((String) value).toLowerCase()); } else { throw new JsonLdError(Error.INVALID_DEFAULT_LANGUAGE, value); } } // 3.7 final Map<String, Boolean> defined = new LinkedHashMap<>(); for (final String key : ((Map<String, Object>) context).keySet()) { if ("@base".equals(key) || "@vocab".equals(key) || "@language".equals(key)) { continue; } result.createTermDefinition((Map<String, Object>) context, key, defined); } } return result; } public Context parse(Object localContext) throws JsonLdError { return this.parse(localContext, new ArrayList<String>()); } /** * Create Term Definition Algorithm * * http://json-ld.org/spec/latest/json-ld-api/#create-term-definition * * @param result * @param context * @param key * @param defined * @throws JsonLdError */ private void createTermDefinition(Map<String, Object> context, String term, Map<String, Boolean> defined) throws JsonLdError { if (defined.containsKey(term)) { if (Boolean.TRUE.equals(defined.get(term))) { return; } throw new JsonLdError(Error.CYCLIC_IRI_MAPPING, term); } defined.put(term, false); if (JsonLdUtils.isKeyword(term)) { throw new JsonLdError(Error.KEYWORD_REDEFINITION, term); } this.termDefinitions.remove(term); Object value = context.get(term); if (value == null || (value instanceof Map && ((Map<String, Object>) value).containsKey("@id") && ((Map<String, Object>) value) .get("@id") == null)) { this.termDefinitions.put(term, null); defined.put(term, true); return; } if (value instanceof String) { value = newMap("@id", value); } if (!(value instanceof Map)) { throw new JsonLdError(Error.INVALID_TERM_DEFINITION, value); } // casting the value so it doesn't have to be done below everytime final Map<String, Object> val = (Map<String, Object>) value; // 9) create a new term definition final Map<String, Object> definition = newMap(); // 10) if (val.containsKey("@type")) { if (!(val.get("@type") instanceof String)) { throw new JsonLdError(Error.INVALID_TYPE_MAPPING, val.get("@type")); } String type = (String) val.get("@type"); try { type = this.expandIri((String) val.get("@type"), false, true, context, defined); } catch (final JsonLdError error) { if (error.getType() != Error.INVALID_IRI_MAPPING) { throw error; } throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type); } // TODO: fix check for absoluteIri (blank nodes shouldn't count, at // least not here!) if ("@id".equals(type) || "@vocab".equals(type) || (!type.startsWith("_:") && JsonLdUtils.isAbsoluteIri(type))) { definition.put("@type", type); } else { throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type); } } // 11) if (val.containsKey("@reverse")) { if (val.containsKey("@id")) { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY, val); } if (!(val.get("@reverse") instanceof String)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "Expected String for @reverse value. got " + (val.get("@reverse") == null ? "null" : val.get("@reverse") .getClass())); } final String reverse = this.expandIri((String) val.get("@reverse"), false, true, context, defined); if (!JsonLdUtils.isAbsoluteIri(reverse)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "Non-absolute @reverse IRI: " + reverse); } definition.put("@id", reverse); if (val.containsKey("@container")) { final String container = (String) val.get("@container"); if (container == null || "@set".equals(container) || "@index".equals(container)) { definition.put("@container", container); } else { throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY, "reverse properties only support set- and index-containers"); } } definition.put("@reverse", true); this.termDefinitions.put(term, definition); defined.put(term, true); return; } // 12) definition.put("@reverse", false); // 13) if (val.get("@id") != null && !term.equals(val.get("@id"))) { if (!(val.get("@id") instanceof String)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "expected value of @id to be a string"); } final String res = this.expandIri((String) val.get("@id"), false, true, context, defined); if (JsonLdUtils.isKeyword(res) || JsonLdUtils.isAbsoluteIri(res)) { if ("@context".equals(res)) { throw new JsonLdError(Error.INVALID_KEYWORD_ALIAS, "cannot alias @context"); } definition.put("@id", res); } else { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "resulting IRI mapping should be a keyword, absolute IRI or blank node"); } } // 14) else if (term.indexOf(":") >= 0) { final int colIndex = term.indexOf(":"); final String prefix = term.substring(0, colIndex); final String suffix = term.substring(colIndex + 1); if (context.containsKey(prefix)) { this.createTermDefinition(context, prefix, defined); } if (termDefinitions.containsKey(prefix)) { definition.put("@id", ((Map<String, Object>) termDefinitions.get(prefix)).get("@id") + suffix); } else { definition.put("@id", term); } // 15) } else if (this.containsKey("@vocab")) { definition.put("@id", this.get("@vocab") + term); } else { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "relative term definition without vocab mapping"); } // 16) if (val.containsKey("@container")) { final String container = (String) val.get("@container"); if (!"@list".equals(container) && !"@set".equals(container) && !"@index".equals(container) && !"@language".equals(container)) { throw new JsonLdError(Error.INVALID_CONTAINER_MAPPING, "@container must be either @list, @set, @index, or @language"); } definition.put("@container", container); } // 17) if (val.containsKey("@language") && !val.containsKey("@type")) { if (val.get("@language") == null || val.get("@language") instanceof String) { final String language = (String) val.get("@language"); definition.put("@language", language != null ? language.toLowerCase() : null); } else { throw new JsonLdError(Error.INVALID_LANGUAGE_MAPPING, "@language must be a string or null"); } } // 18) this.termDefinitions.put(term, definition); defined.put(term, true); } /** * IRI Expansion Algorithm * * http://json-ld.org/spec/latest/json-ld-api/#iri-expansion * * @param value * @param relative * @param vocab * @param context * @param defined * @return * @throws JsonLdError */ String expandIri(String value, boolean relative, boolean vocab, Map<String, Object> context, Map<String, Boolean> defined) throws JsonLdError { // 1) if (value == null || JsonLdUtils.isKeyword(value)) { return value; } // 2) if (context != null && context.containsKey(value) && !Boolean.TRUE.equals(defined.get(value))) { this.createTermDefinition(context, value, defined); } // 3) if (vocab && this.termDefinitions.containsKey(value)) { final Map<String, Object> td = (LinkedHashMap<String, Object>) this.termDefinitions .get(value); if (td != null) { return (String) td.get("@id"); } else { return null; } } // 4) final int colIndex = value.indexOf(":"); if (colIndex >= 0) { // 4.1) final String prefix = value.substring(0, colIndex); final String suffix = value.substring(colIndex + 1); // 4.2) if ("_".equals(prefix) || suffix.startsWith("//")) { return value; } // 4.3) if (context != null && context.containsKey(prefix) && (!defined.containsKey(prefix) || defined.get(prefix) == false)) { this.createTermDefinition(context, prefix, defined); } // 4.4) if (this.termDefinitions.containsKey(prefix)) { return (String) ((LinkedHashMap<String, Object>) this.termDefinitions.get(prefix)) .get("@id") + suffix; } // 4.5) return value; } // 5) if (vocab && this.containsKey("@vocab")) { return this.get("@vocab") + value; } // 6) else if (relative) { return JsonLdUrl.resolve((String) this.get("@base"), value); } else if (context != null && JsonLdUtils.isRelativeIri(value)) { throw new JsonLdError(Error.INVALID_IRI_MAPPING, "not an absolute IRI: " + value); } // 7) return value; } /** * IRI Compaction Algorithm * * http://json-ld.org/spec/latest/json-ld-api/#iri-compaction * * Compacts an IRI or keyword into a term or prefix if it can be. If the IRI * has an associated value it may be passed. * * @param iri * the IRI to compact. * @param value * the value to check or null. * @param relativeTo * options for how to compact IRIs: vocab: true to split after * @vocab, false not to. * @param reverse * true if a reverse property is being compacted, false if not. * * @return the compacted term, prefix, keyword alias, or the original IRI. */ String compactIri(String iri, Object value, boolean relativeToVocab, boolean reverse) { // 1) if (iri == null) { return null; } // 2) if (relativeToVocab && getInverse().containsKey(iri)) { // 2.1) String defaultLanguage = (String) this.get("@language"); if (defaultLanguage == null) { defaultLanguage = "@none"; } // 2.2) final List<String> containers = new ArrayList<>(); // 2.3) String typeLanguage = "@language"; String typeLanguageValue = "@null"; // 2.4) if (value instanceof Map && ((Map<String, Object>) value).containsKey("@index")) { containers.add("@index"); } // 2.5) if (reverse) { typeLanguage = "@type"; typeLanguageValue = "@reverse"; containers.add("@set"); } // 2.6) else if (value instanceof Map && ((Map<String, Object>) value).containsKey("@list")) { // 2.6.1) if (!((Map<String, Object>) value).containsKey("@index")) { containers.add("@list"); } // 2.6.2) final List<Object> list = (List<Object>) ((Map<String, Object>) value).get("@list"); // 2.6.3) String commonLanguage = (list.isEmpty()) ? defaultLanguage : null; String commonType = null; // 2.6.4) for (final Object item : list) { // 2.6.4.1) String itemLanguage = "@none"; String itemType = "@none"; // 2.6.4.2) if (JsonLdUtils.isValue(item)) { // 2.6.4.2.1) if (((Map<String, Object>) item).containsKey("@language")) { itemLanguage = (String) ((Map<String, Object>) item).get("@language"); } // 2.6.4.2.2) else if (((Map<String, Object>) item).containsKey("@type")) { itemType = (String) ((Map<String, Object>) item).get("@type"); } // 2.6.4.2.3) else { itemLanguage = "@null"; } } // 2.6.4.3) else { itemType = "@id"; } // 2.6.4.4) if (commonLanguage == null) { commonLanguage = itemLanguage; } // 2.6.4.5) else if (!commonLanguage.equals(itemLanguage) && JsonLdUtils.isValue(item)) { commonLanguage = "@none"; } // 2.6.4.6) if (commonType == null) { commonType = itemType; } // 2.6.4.7) else if (!commonType.equals(itemType)) { commonType = "@none"; } // 2.6.4.8) if ("@none".equals(commonLanguage) && "@none".equals(commonType)) { break; } } // 2.6.5) commonLanguage = (commonLanguage != null) ? commonLanguage : "@none"; // 2.6.6) commonType = (commonType != null) ? commonType : "@none"; // 2.6.7) if (!"@none".equals(commonType)) { typeLanguage = "@type"; typeLanguageValue = commonType; } // 2.6.8) else { typeLanguageValue = commonLanguage; } } // 2.7) else { // 2.7.1) if (value instanceof Map && ((Map<String, Object>) value).containsKey("@value")) { // 2.7.1.1) if (((Map<String, Object>) value).containsKey("@language") && !((Map<String, Object>) value).containsKey("@index")) { containers.add("@language"); typeLanguageValue = (String) ((Map<String, Object>) value).get("@language"); } // 2.7.1.2) else if (((Map<String, Object>) value).containsKey("@type")) { typeLanguage = "@type"; typeLanguageValue = (String) ((Map<String, Object>) value).get("@type"); } else { typeLanguage = "@type"; typeLanguageValue = "@id"; } } // 2.7.2) else { typeLanguage = "@type"; typeLanguageValue = "@id"; } // 2.7.3) containers.add("@set"); } // 2.8) containers.add("@none"); // 2.9) if (typeLanguageValue == null) { typeLanguageValue = "@null"; } // 2.10) final List<String> preferredValues = new ArrayList<>(); // 2.11) if ("@reverse".equals(typeLanguageValue)) { preferredValues.add("@reverse"); } // 2.12) if (("@reverse".equals(typeLanguageValue) || "@id".equals(typeLanguageValue)) && (value instanceof Map) && ((Map<String, Object>) value).containsKey("@id")) { // 2.12.1) final String result = this.compactIri( (String) ((Map<String, Object>) value).get("@id"), null, true, true); if (termDefinitions.containsKey(result) && ((Map<String, Object>) termDefinitions.get(result)).containsKey("@id") && ((Map<String, Object>) value).get("@id").equals( ((Map<String, Object>) termDefinitions.get(result)).get("@id"))) { preferredValues.add("@vocab"); preferredValues.add("@id"); } // 2.12.2) else { preferredValues.add("@id"); preferredValues.add("@vocab"); } } // 2.13) else { preferredValues.add(typeLanguageValue); } preferredValues.add("@none"); // 2.14) final String term = selectTerm(iri, containers, typeLanguage, preferredValues); // 2.15) if (term != null) { return term; } } // 3) if (relativeToVocab && this.containsKey("@vocab")) { // determine if vocab is a prefix of the iri final String vocab = (String) this.get("@vocab"); // 3.1) if (iri.indexOf(vocab) == 0 && !iri.equals(vocab)) { // use suffix as relative iri if it is not a term in the // active context final String suffix = iri.substring(vocab.length()); if (!termDefinitions.containsKey(suffix)) { return suffix; } } } // 4) String compactIRI = null; // 5) for (final Map.Entry<String, Object> stringObjectEntry : termDefinitions.entrySet()) { final Map<String, Object> termDefinition = (Map<String, Object>) stringObjectEntry.getValue(); // 5.1) if (stringObjectEntry.getKey().contains(":")) { continue; } // 5.2) if (termDefinition == null || iri.equals(termDefinition.get("@id")) || !iri.startsWith((String) termDefinition.get("@id"))) { continue; } // 5.3) final String candidate = stringObjectEntry.getKey() + ":" + iri.substring(((String) termDefinition.get("@id")).length()); // 5.4) if ((compactIRI == null || compareShortestLeast(candidate, compactIRI) < 0) && (!termDefinitions.containsKey(candidate) || (iri .equals(((Map<String, Object>) termDefinitions.get(candidate)) .get("@id")) && value == null))) { compactIRI = candidate; } } // 6) if (compactIRI != null) { return compactIRI; } // 7) if (!relativeToVocab) { return JsonLdUrl.removeBase(this.get("@base"), iri); } // 8) return iri; } /** * Return a map of potential RDF prefixes based on the JSON-LD Term * Definitions in this context. * <p> * No guarantees of the prefixes are given, beyond that it will not contain * ":". * * @param onlyCommonPrefixes * If <code>true</code>, the result will not include * "not so useful" prefixes, such as "term1": * "http://example.com/term1", e.g. all IRIs will end with "/" or * "#". If <code>false</code>, all potential prefixes are * returned. * * @return A map from prefix string to IRI string */ public Map<String, String> getPrefixes(boolean onlyCommonPrefixes) { final Map<String, String> prefixes = new LinkedHashMap<>(); for (final Map.Entry<String, Object> stringObjectEntry : termDefinitions.entrySet()) { if (stringObjectEntry.getKey().contains(":")) { continue; } final Map<String, Object> termDefinition = (Map<String, Object>) stringObjectEntry.getValue(); if (termDefinition == null) { continue; } final String id = (String) termDefinition.get("@id"); if (id == null) { continue; } if (stringObjectEntry.getKey().startsWith("@") || id.startsWith("@")) { continue; } if (!onlyCommonPrefixes || id.endsWith("/") || id.endsWith("#")) { prefixes.put(stringObjectEntry.getKey(), id); } } return prefixes; } String compactIri(String iri, boolean relativeToVocab) { return compactIri(iri, null, relativeToVocab, false); } String compactIri(String iri) { return compactIri(iri, null, false, false); } @Override public Context clone() { final Context rval = (Context) super.clone(); // TODO: is this shallow copy enough? probably not, but it passes all // the tests! rval.termDefinitions = new LinkedHashMap<>(this.termDefinitions); return rval; } /** * Inverse Context Creation * * http://json-ld.org/spec/latest/json-ld-api/#inverse-context-creation * * Generates an inverse context for use in the compaction algorithm, if not * already generated for the given active context. * * @return the inverse context. */ public Map<String, Object> getInverse() { // lazily create inverse if (inverse != null) { return inverse; } // 1) inverse = newMap(); // 2) String defaultLanguage = (String) this.get("@language"); if (defaultLanguage == null) { defaultLanguage = "@none"; } // create term selections for each mapping in the context, ordererd by // shortest and then lexicographically least final List<String> terms = new ArrayList<>(termDefinitions.keySet()); Collections.sort(terms, new Comparator<String>() { @Override public int compare(String a, String b) { return compareShortestLeast(a, b); } }); for (final String term : terms) { final Map<String, Object> definition = (Map<String, Object>) termDefinitions.get(term); // 3.1) if (definition == null) { continue; } // 3.2) String container = (String) definition.get("@container"); if (container == null) { container = "@none"; } // 3.3) final String iri = (String) definition.get("@id"); // 3.4 + 3.5) Map<String, Object> containerMap = (Map<String, Object>) inverse.get(iri); if (containerMap == null) { containerMap = newMap(); inverse.put(iri, containerMap); } // 3.6 + 3.7) Map<String, Object> typeLanguageMap = (Map<String, Object>) containerMap.get(container); if (typeLanguageMap == null) { typeLanguageMap = newMap(); typeLanguageMap.put("@language", newMap()); typeLanguageMap.put("@type", newMap()); containerMap.put(container, typeLanguageMap); } // 3.8) if (Boolean.TRUE.equals(definition.get("@reverse"))) { final Map<String, Object> typeMap = (Map<String, Object>) typeLanguageMap .get("@type"); if (!typeMap.containsKey("@reverse")) { typeMap.put("@reverse", term); } // 3.9) } else if (definition.containsKey("@type")) { final Map<String, Object> typeMap = (Map<String, Object>) typeLanguageMap .get("@type"); if (!typeMap.containsKey(definition.get("@type"))) { typeMap.put((String) definition.get("@type"), term); } // 3.10) } else if (definition.containsKey("@language")) { final Map<String, Object> languageMap = (Map<String, Object>) typeLanguageMap .get("@language"); String language = (String) definition.get("@language"); if (language == null) { language = "@null"; } if (!languageMap.containsKey(language)) { languageMap.put(language, term); } // 3.11) } else { // 3.11.1) final Map<String, Object> languageMap = (Map<String, Object>) typeLanguageMap .get("@language"); // 3.11.2) if (!languageMap.containsKey("@language")) { languageMap.put("@language", term); } // 3.11.3) if (!languageMap.containsKey("@none")) { languageMap.put("@none", term); } // 3.11.4) final Map<String, Object> typeMap = (Map<String, Object>) typeLanguageMap .get("@type"); // 3.11.5) if (!typeMap.containsKey("@none")) { typeMap.put("@none", term); } } } // 4) return inverse; } /** * Term Selection * * http://json-ld.org/spec/latest/json-ld-api/#term-selection * * This algorithm, invoked via the IRI Compaction algorithm, makes use of an * active context's inverse context to find the term that is best used to * compact an IRI. Other information about a value associated with the IRI * is given, including which container mappings and which type mapping or * language mapping would be best used to express the value. * * @return the selected term. */ private String selectTerm(String iri, List<String> containers, String typeLanguage, List<String> preferredValues) { final Map<String, Object> inv = getInverse(); // 1) final Map<String, Object> containerMap = (Map<String, Object>) inv.get(iri); // 2) for (final String container : containers) { // 2.1) if (!containerMap.containsKey(container)) { continue; } // 2.2) final Map<String, Object> typeLanguageMap = (Map<String, Object>) containerMap .get(container); // 2.3) final Map<String, Object> valueMap = (Map<String, Object>) typeLanguageMap .get(typeLanguage); // 2.4 ) for (final String item : preferredValues) { // 2.4.1 if (!valueMap.containsKey(item)) { continue; } // 2.4.2 return (String) valueMap.get(item); } } // 3) return null; } /** * Retrieve container mapping. * * @param property * The Property to get a container mapping for. * @return The container mapping */ public String getContainer(String property) { if ("@graph".equals(property)) { return "@set"; } if (JsonLdUtils.isKeyword(property)) { return property; } final Map<String, Object> td = (Map<String, Object>) termDefinitions.get(property); if (td == null) { return null; } return (String) td.get("@container"); } public Boolean isReverseProperty(String property) { final Map<String, Object> td = (Map<String, Object>) termDefinitions.get(property); if (td == null) { return false; } final Object reverse = td.get("@reverse"); return reverse != null && (Boolean) reverse; } private String getTypeMapping(String property) { final Map<String, Object> td = (Map<String, Object>) termDefinitions.get(property); if (td == null) { return null; } return (String) td.get("@type"); } private String getLanguageMapping(String property) { final Map<String, Object> td = (Map<String, Object>) termDefinitions.get(property); if (td == null) { return null; } return (String) td.get("@language"); } Map<String, Object> getTermDefinition(String key) { return ((Map<String, Object>) termDefinitions.get(key)); } public Object expandValue(String activeProperty, Object value) throws JsonLdError { final Map<String, Object> rval = newMap(); final Map<String, Object> td = getTermDefinition(activeProperty); // 1) if (td != null && "@id".equals(td.get("@type"))) { // TODO: i'm pretty sure value should be a string if the @type is // @id rval.put("@id", expandIri(value.toString(), true, false, null, null)); return rval; } // 2) if (td != null && "@vocab".equals(td.get("@type"))) { // TODO: same as above rval.put("@id", expandIri(value.toString(), true, true, null, null)); return rval; } // 3) rval.put("@value", value); // 4) if (td != null && td.containsKey("@type")) { rval.put("@type", td.get("@type")); } // 5) else if (value instanceof String) { // 5.1) if (td != null && td.containsKey("@language")) { final String lang = (String) td.get("@language"); if (lang != null) { rval.put("@language", lang); } } // 5.2) else if (this.get("@language") != null) { rval.put("@language", this.get("@language")); } } return rval; } public Object getContextValue(String activeProperty, String string) throws JsonLdError { throw new JsonLdError(Error.NOT_IMPLEMENTED, "getContextValue is only used by old code so far and thus isn't implemented"); } public Map<String, Object> serialize() { final Map<String, Object> ctx = newMap(); if (this.get("@base") != null && !this.get("@base").equals(options.getBase())) { ctx.put("@base", this.get("@base")); } if (this.get("@language") != null) { ctx.put("@language", this.get("@language")); } if (this.get("@vocab") != null) { ctx.put("@vocab", this.get("@vocab")); } for (final Map.Entry<String, Object> stringObjectEntry : termDefinitions.entrySet()) { final Map<String, Object> definition = (Map<String, Object>) stringObjectEntry.getValue(); if (definition.get("@language") == null && definition.get("@container") == null && definition.get("@type") == null && (definition.get("@reverse") == null || Boolean.FALSE.equals(definition .get("@reverse")))) { final String cid = this.compactIri((String) definition.get("@id")); ctx.put(stringObjectEntry.getKey(), stringObjectEntry.getKey().equals(cid) ? definition.get("@id") : cid); } else { final Map<String, Object> defn = newMap(); final String cid = this.compactIri((String) definition.get("@id")); final Boolean reverseProperty = Boolean.TRUE.equals(definition.get("@reverse")); if (!(stringObjectEntry.getKey().equals(cid) && !reverseProperty)) { defn.put(reverseProperty ? "@reverse" : "@id", cid); } final String typeMapping = (String) definition.get("@type"); if (typeMapping != null) { defn.put("@type", JsonLdUtils.isKeyword(typeMapping) ? typeMapping : compactIri(typeMapping, true)); } if (definition.get("@container") != null) { defn.put("@container", definition.get("@container")); } final Object lang = definition.get("@language"); if (definition.get("@language") != null) { defn.put("@language", Boolean.FALSE.equals(lang) ? null : lang); } ctx.put(stringObjectEntry.getKey(), defn); } } final Map<String, Object> rval = newMap(); if (!(ctx == null || ctx.isEmpty())) { rval.put("@context", ctx); } return rval; } }