docs: private Context parse(Object localContext, List<String> remoteContexts,
              boolean parsingARemoteContext) throws JsonLdError {
          if (remoteContexts == null) {
              remoteContexts = new ArrayList<String>();
          }
          // 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<Object>();
              ((List<Object>) localContext).add(temp);
          }
          // 3)
          for (final 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(JsonLdConsts.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.getDocument();
                  if (!(remoteContext instanceof Map) || !((Map<String, Object>) remoteContext)
                          .containsKey(JsonLdConsts.CONTEXT)) {
                      // If the dereferenced document has no top-level JSON object
                      // with an @context member
                      throw new JsonLdError(Error.INVALID_REMOTE_CONTEXT, context);
                  }
                  final Object tempContext = ((Map<String, Object>) remoteContext)
                          .get(JsonLdConsts.CONTEXT);
  
                  // 3.2.4
                  result = result.parse(tempContext, remoteContexts, true);
                  // 3.2.5
                  continue;
              } else if (!(context instanceof Map)) {
                  // 3.3
                  throw new JsonLdError(Error.INVALID_LOCAL_CONTEXT, context);
              }
              checkEmptyKey((Map<String, Object>) context);
              // 3.4
              if (!parsingARemoteContext
                      && ((Map<String, Object>) context).containsKey(JsonLdConsts.BASE)) {
                  // 3.4.1
                  final Object value = ((Map<String, Object>) context).get(JsonLdConsts.BASE);
                  // 3.4.2
                  if (value == null) {
                      result.remove(JsonLdConsts.BASE);
                  } else if (value instanceof String) {
                      // 3.4.3
                      if (JsonLdUtils.isAbsoluteIri((String) value)) {
                          result.put(JsonLdConsts.BASE, value);
                      } else {
                          // 3.4.4
                          final String baseUri = (String) result.get(JsonLdConsts.BASE);
                          if (!JsonLdUtils.isAbsoluteIri(baseUri)) {
                              throw new JsonLdError(Error.INVALID_BASE_IRI, baseUri);
                          }
                          result.put(JsonLdConsts.BASE, JsonLdUrl.resolve(baseUri, (String) value));
                      }
                  } else {
                      // 3.4.5
                      throw new JsonLdError(JsonLdError.Error.INVALID_BASE_IRI,
                              "@base must be a string");
                  }
              }
  
              // 3.5
              if (((Map<String, Object>) context).containsKey(JsonLdConsts.VOCAB)) {
                  final Object value = ((Map<String, Object>) context).get(JsonLdConsts.VOCAB);
                  if (value == null) {
                      result.remove(JsonLdConsts.VOCAB);
                  } else if (value instanceof String) {
                      if (JsonLdUtils.isAbsoluteIri((String) value)) {
                          result.put(JsonLdConsts.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(JsonLdConsts.LANGUAGE)) {
                  final Object value = ((Map<String, Object>) context).get(JsonLdConsts.LANGUAGE);
                  if (value == null) {
                      result.remove(JsonLdConsts.LANGUAGE);
                  } else if (value instanceof String) {
                      result.put(JsonLdConsts.LANGUAGE, ((String) value).toLowerCase());
                  } else {
                      throw new JsonLdError(Error.INVALID_DEFAULT_LANGUAGE, value);
                  }
              }
  
              // 3.7
              final Map<String, Boolean> defined = new LinkedHashMap<String, Boolean>();
              for (final String key : ((Map<String, Object>) context).keySet()) {
                  if (JsonLdConsts.BASE.equals(key) || JsonLdConsts.VOCAB.equals(key)
                          || JsonLdConsts.LANGUAGE.equals(key)) {
                      continue;
                  }
                  result.createTermDefinition((Map<String, Object>) context, key, defined);
              }
          }
          return result;
      }