docs: 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)
                  && !(options.getAllowContainerSetOnType() && JsonLdConsts.TYPE.equals(term)
                          && !(context.get(term)).toString().contains(JsonLdConsts.ID))) {
              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(JsonLdConsts.ID)
                  && ((Map<String, Object>) value).get(JsonLdConsts.ID) == null)) {
              this.termDefinitions.put(term, null);
              defined.put(term, true);
              return;
          }
  
          if (value instanceof String) {
              value = newMap(JsonLdConsts.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(JsonLdConsts.TYPE)) {
              if (!(val.get(JsonLdConsts.TYPE) instanceof String)) {
                  throw new JsonLdError(Error.INVALID_TYPE_MAPPING, val.get(JsonLdConsts.TYPE));
              }
              String type = (String) val.get(JsonLdConsts.TYPE);
              try {
                  type = this.expandIri((String) val.get(JsonLdConsts.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, error);
              }
              // TODO: fix check for absoluteIri (blank nodes shouldn't count, at
              // least not here!)
              if (JsonLdConsts.ID.equals(type) || JsonLdConsts.VOCAB.equals(type)
                      || (!type.startsWith(JsonLdConsts.BLANK_NODE_PREFIX)
                              && JsonLdUtils.isAbsoluteIri(type))) {
                  definition.put(JsonLdConsts.TYPE, type);
              } else {
                  throw new JsonLdError(Error.INVALID_TYPE_MAPPING, type);
              }
          }
  
          // 11)
          if (val.containsKey(JsonLdConsts.REVERSE)) {
              if (val.containsKey(JsonLdConsts.ID)) {
                  throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY, val);
              }
              if (!(val.get(JsonLdConsts.REVERSE) instanceof String)) {
                  throw new JsonLdError(Error.INVALID_IRI_MAPPING,
                          "Expected String for @reverse value. got "
                                  + (val.get(JsonLdConsts.REVERSE) == null ? "null"
                                          : val.get(JsonLdConsts.REVERSE).getClass()));
              }
              final String reverse = this.expandIri((String) val.get(JsonLdConsts.REVERSE), false,
                      true, context, defined);
              if (!JsonLdUtils.isAbsoluteIri(reverse)) {
                  throw new JsonLdError(Error.INVALID_IRI_MAPPING,
                          "Non-absolute @reverse IRI: " + reverse);
              }
              definition.put(JsonLdConsts.ID, reverse);
              if (val.containsKey(JsonLdConsts.CONTAINER)) {
                  final String container = (String) val.get(JsonLdConsts.CONTAINER);
                  if (container == null || JsonLdConsts.SET.equals(container)
                          || JsonLdConsts.INDEX.equals(container)) {
                      definition.put(JsonLdConsts.CONTAINER, container);
                  } else {
                      throw new JsonLdError(Error.INVALID_REVERSE_PROPERTY,
                              "reverse properties only support set- and index-containers");
                  }
              }
              definition.put(JsonLdConsts.REVERSE, true);
              this.termDefinitions.put(term, definition);
              defined.put(term, true);
              return;
          }
  
          // 12)
          definition.put(JsonLdConsts.REVERSE, false);
  
          // 13)
          if (val.get(JsonLdConsts.ID) != null && !term.equals(val.get(JsonLdConsts.ID))) {
              if (!(val.get(JsonLdConsts.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(JsonLdConsts.ID), false, true,
                      context, defined);
              if (JsonLdUtils.isKeyword(res) || JsonLdUtils.isAbsoluteIri(res)) {
                  if (JsonLdConsts.CONTEXT.equals(res)) {
                      throw new JsonLdError(Error.INVALID_KEYWORD_ALIAS, "cannot alias @context");
                  }
                  definition.put(JsonLdConsts.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(JsonLdConsts.ID,
                          ((Map<String, Object>) termDefinitions.get(prefix)).get(JsonLdConsts.ID)
                                  + suffix);
              } else {
                  definition.put(JsonLdConsts.ID, term);
              }
              // 15)
          } else if (this.containsKey(JsonLdConsts.VOCAB)) {
              definition.put(JsonLdConsts.ID, this.get(JsonLdConsts.VOCAB) + term);
          } else if (!JsonLdConsts.TYPE.equals(term)) {
              throw new JsonLdError(Error.INVALID_IRI_MAPPING,
                      "relative term definition without vocab mapping");
          }
  
          // 16)
          if (val.containsKey(JsonLdConsts.CONTAINER)) {
              final String container = (String) val.get(JsonLdConsts.CONTAINER);
              if (!JsonLdConsts.LIST.equals(container) && !JsonLdConsts.SET.equals(container)
                      && !JsonLdConsts.INDEX.equals(container)
                      && !JsonLdConsts.LANGUAGE.equals(container)) {
                  throw new JsonLdError(Error.INVALID_CONTAINER_MAPPING,
                          "@container must be either @list, @set, @index, or @language");
              }
              definition.put(JsonLdConsts.CONTAINER, container);
              if (JsonLdConsts.TYPE.equals(term)) {
                  definition.put(JsonLdConsts.ID, "type");
              }
          }
  
          // 17)
          if (val.containsKey(JsonLdConsts.LANGUAGE) && !val.containsKey(JsonLdConsts.TYPE)) {
              if (val.get(JsonLdConsts.LANGUAGE) == null
                      || val.get(JsonLdConsts.LANGUAGE) instanceof String) {
                  final String language = (String) val.get(JsonLdConsts.LANGUAGE);
                  definition.put(JsonLdConsts.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);
      }