) termDefinitions.get(candidate)).get(JsonLdConsts.ID))
+ && value == null));
+
+ if (condition1 && condition2) {
+ compactIRI = candidate;
+ }
+ return compactIRI;
+ }
+
+ /**
+ * Return a map of potential RDF prefixes based on the JSON-LD Term
+ * Definitions in this context.
+ *
+ * No guarantees of the prefixes are given, beyond that it will not contain
+ * ":".
+ *
+ * @param onlyCommonPrefixes
+ * If true, 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 false,
+ * all potential prefixes are returned.
+ *
+ * @return A map from prefix string to IRI string
+ */
+ public Map getPrefixes(boolean onlyCommonPrefixes) {
+ final Map prefixes = new LinkedHashMap();
+ for (final String term : termDefinitions.keySet()) {
+ if (term.contains(":")) {
+ continue;
+ }
+ final Map termDefinition = (Map) termDefinitions
+ .get(term);
+ if (termDefinition == null) {
+ continue;
+ }
+ final String id = (String) termDefinition.get(JsonLdConsts.ID);
+ if (id == null) {
+ continue;
+ }
+ if (term.startsWith("@") || id.startsWith("@")) {
+ continue;
+ }
+ if (!onlyCommonPrefixes || id.endsWith("/") || id.endsWith("#")) {
+ prefixes.put(term, 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 getInverse() {
+
+ // lazily create inverse
+ if (inverse != null) {
+ return inverse;
+ }
+
+ // 1)
+ inverse = newMap();
+
+ // 2)
+ String defaultLanguage = (String) this.get(JsonLdConsts.LANGUAGE);
+ if (defaultLanguage == null) {
+ defaultLanguage = JsonLdConsts.NONE;
+ }
+
+ // create term selections for each mapping in the context, ordererd by
+ // shortest and then lexicographically least
+ final List terms = new ArrayList(termDefinitions.keySet());
+ Collections.sort(terms, new Comparator() {
+ @Override
+ public int compare(String a, String b) {
+ return compareShortestLeast(a, b);
+ }
+ });
+
+ for (final String term : terms) {
+ final Map definition = (Map) termDefinitions.get(term);
+ // 3.1)
+ if (definition == null) {
+ continue;
+ }
+
+ // 3.2)
+ String container = (String) definition.get(JsonLdConsts.CONTAINER);
+ if (container == null) {
+ container = JsonLdConsts.NONE;
+ }
+
+ // 3.3)
+ final String iri = (String) definition.get(JsonLdConsts.ID);
+
+ // 3.4 + 3.5)
+ Map containerMap = (Map) inverse.get(iri);
+ if (containerMap == null) {
+ containerMap = newMap();
+ inverse.put(iri, containerMap);
+ }
+
+ // 3.6 + 3.7)
+ Map typeLanguageMap = (Map) containerMap.get(container);
+ if (typeLanguageMap == null) {
+ typeLanguageMap = newMap();
+ typeLanguageMap.put(JsonLdConsts.LANGUAGE, newMap());
+ typeLanguageMap.put(JsonLdConsts.TYPE, newMap());
+ containerMap.put(container, typeLanguageMap);
+ }
+
+ // 3.8)
+ if (Boolean.TRUE.equals(definition.get(JsonLdConsts.REVERSE))) {
+ final Map typeMap = (Map) typeLanguageMap
+ .get(JsonLdConsts.TYPE);
+ if (!typeMap.containsKey(JsonLdConsts.REVERSE)) {
+ typeMap.put(JsonLdConsts.REVERSE, term);
+ }
+ // 3.9)
+ } else if (definition.containsKey(JsonLdConsts.TYPE)) {
+ final Map typeMap = (Map) typeLanguageMap
+ .get(JsonLdConsts.TYPE);
+ if (!typeMap.containsKey(definition.get(JsonLdConsts.TYPE))) {
+ typeMap.put((String) definition.get(JsonLdConsts.TYPE), term);
+ }
+ // 3.10)
+ } else if (definition.containsKey(JsonLdConsts.LANGUAGE)) {
+ final Map languageMap = (Map) typeLanguageMap
+ .get(JsonLdConsts.LANGUAGE);
+ String language = (String) definition.get(JsonLdConsts.LANGUAGE);
+ if (language == null) {
+ language = JsonLdConsts.NULL;
+ }
+ if (!languageMap.containsKey(language)) {
+ languageMap.put(language, term);
+ }
+ // 3.11)
+ } else {
+ // 3.11.1)
+ final Map languageMap = (Map) typeLanguageMap
+ .get(JsonLdConsts.LANGUAGE);
+ // 3.11.2)
+ if (!languageMap.containsKey(JsonLdConsts.LANGUAGE)) {
+ languageMap.put(JsonLdConsts.LANGUAGE, term);
+ }
+ // 3.11.3)
+ if (!languageMap.containsKey(JsonLdConsts.NONE)) {
+ languageMap.put(JsonLdConsts.NONE, term);
+ }
+ // 3.11.4)
+ final Map typeMap = (Map) typeLanguageMap
+ .get(JsonLdConsts.TYPE);
+ // 3.11.5)
+ if (!typeMap.containsKey(JsonLdConsts.NONE)) {
+ typeMap.put(JsonLdConsts.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 containers, String typeLanguage,
+ List preferredValues) {
+ final Map inv = getInverse();
+ // 1)
+ final Map containerMap = (Map) inv.get(iri);
+ // 2)
+ for (final String container : containers) {
+ // 2.1)
+ if (!containerMap.containsKey(container)) {
+ continue;
+ }
+ // 2.2)
+ final Map typeLanguageMap = (Map) containerMap
+ .get(container);
+ // 2.3)
+ final Map valueMap = (Map) 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 if any, else null
+ */
+ public String getContainer(String property) {
+ if (property == null) {
+ return null;
+ }
+ if (JsonLdConsts.GRAPH.equals(property)) {
+ return JsonLdConsts.SET;
+ }
+ if (!property.equals(JsonLdConsts.TYPE) && JsonLdUtils.isKeyword(property)) {
+ return property;
+ }
+ final Map td = (Map) termDefinitions.get(property);
+ if (td == null) {
+ return null;
+ }
+ return (String) td.get(JsonLdConsts.CONTAINER);
+ }
+
+ public Boolean isReverseProperty(String property) {
+ final Map td = (Map) termDefinitions.get(property);
+ if (td == null) {
+ return false;
+ }
+ final Object reverse = td.get(JsonLdConsts.REVERSE);
+ return reverse != null && (Boolean) reverse;
+ }
+
+ public String getTypeMapping(String property) {
+ final Map td = (Map) termDefinitions.get(property);
+ if (td == null) {
+ return null;
+ }
+ return (String) td.get(JsonLdConsts.TYPE);
+ }
+
+ public String getLanguageMapping(String property) {
+ final Map td = (Map) termDefinitions.get(property);
+ if (td == null) {
+ return null;
+ }
+ return (String) td.get(JsonLdConsts.LANGUAGE);
+ }
+
+ Map getTermDefinition(String key) {
+ return ((Map) termDefinitions.get(key));
+ }
+
+ public Object expandValue(String activeProperty, Object value) throws JsonLdError {
+ final Map rval = newMap();
+ final Map td = getTermDefinition(activeProperty);
+ // 1)
+ if (td != null && JsonLdConsts.ID.equals(td.get(JsonLdConsts.TYPE))) {
+ // TODO: i'm pretty sure value should be a string if the @type is
+ // @id
+ rval.put(JsonLdConsts.ID, expandIri(value.toString(), true, false, null, null));
+ return rval;
+ }
+ // 2)
+ if (td != null && JsonLdConsts.VOCAB.equals(td.get(JsonLdConsts.TYPE))) {
+ // TODO: same as above
+ rval.put(JsonLdConsts.ID, expandIri(value.toString(), true, true, null, null));
+ return rval;
+ }
+ // 3)
+ rval.put(JsonLdConsts.VALUE, value);
+ // 4)
+ if (td != null && td.containsKey(JsonLdConsts.TYPE)) {
+ rval.put(JsonLdConsts.TYPE, td.get(JsonLdConsts.TYPE));
+ }
+ // 5)
+ else if (value instanceof String) {
+ // 5.1)
+ if (td != null && td.containsKey(JsonLdConsts.LANGUAGE)) {
+ final String lang = (String) td.get(JsonLdConsts.LANGUAGE);
+ if (lang != null) {
+ rval.put(JsonLdConsts.LANGUAGE, lang);
+ }
+ }
+ // 5.2)
+ else if (this.get(JsonLdConsts.LANGUAGE) != null) {
+ rval.put(JsonLdConsts.LANGUAGE, this.get(JsonLdConsts.LANGUAGE));
+ }
+ }
+ return rval;
+ }
+
+ @Deprecated
+ public Map serialize() {
+ final Map ctx = newMap();
+ if (this.get(JsonLdConsts.BASE) != null
+ && !this.get(JsonLdConsts.BASE).equals(options.getBase())) {
+ ctx.put(JsonLdConsts.BASE, this.get(JsonLdConsts.BASE));
+ }
+ if (this.get(JsonLdConsts.LANGUAGE) != null) {
+ ctx.put(JsonLdConsts.LANGUAGE, this.get(JsonLdConsts.LANGUAGE));
+ }
+ if (this.get(JsonLdConsts.VOCAB) != null) {
+ ctx.put(JsonLdConsts.VOCAB, this.get(JsonLdConsts.VOCAB));
+ }
+ for (final String term : termDefinitions.keySet()) {
+ final Map definition = (Map