docs: public Object hashBlankNodes(Collection<String> unnamed_) throws JsonLdError {
          List<String> unnamed = new ArrayList<String>(unnamed_);
          List<String> nextUnnamed = new ArrayList<String>();
          Map<String, List<String>> duplicates = new LinkedHashMap<String, List<String>>();
          Map<String, String> unique = new LinkedHashMap<String, String>();
  
          // NOTE: not using the same structure as javascript here to avoid
          // possible stack overflows
          // hash quads for each unnamed bnode
          for (int hui = 0;; hui++) {
              if (hui == unnamed.size()) {
                  // done, name blank nodes
                  Boolean named = false;
                  List<String> hashes = new ArrayList<String>(unique.keySet());
                  Collections.sort(hashes);
                  for (final String hash : hashes) {
                      final String bnode = unique.get(hash);
                      namer.getName(bnode);
                      named = true;
                  }
  
                  // continue to hash bnodes if a bnode was assigned a name
                  if (named) {
                      // this resets the initial variables, so it seems like it
                      // has to go on the stack
                      // but since this is the end of the function either way, it
                      // might not have to
                      // hashBlankNodes(unnamed);
                      hui = -1;
                      unnamed = nextUnnamed;
                      nextUnnamed = new ArrayList<String>();
                      duplicates = new LinkedHashMap<String, List<String>>();
                      unique = new LinkedHashMap<String, String>();
                      continue;
                  }
                  // name the duplicate hash bnods
                  else {
                      // names duplicate hash bnodes
                      // enumerate duplicate hash groups in sorted order
                      hashes = new ArrayList<String>(duplicates.keySet());
                      Collections.sort(hashes);
  
                      // process each group
                      for (int pgi = 0;; pgi++) {
                          if (pgi == hashes.size()) {
                              // done, create JSON-LD array
                              // return createArray();
                              final List<String> normalized = new ArrayList<String>();
  
                              // Note: At this point all bnodes in the set of RDF
                              // quads have been
                              // assigned canonical names, which have been stored
                              // in the 'namer' object.
                              // Here each quad is updated by assigning each of
                              // its bnodes its new name
                              // via the 'namer' object
  
                              // update bnode names in each quad and serialize
                              for (int cai = 0; cai < quads.size(); ++cai) {
                                  final Map<String, Object> quad = (Map<String, Object>) quads
                                          .get(cai);
                                  for (final String attr : new String[] { "subject", "object",
                                          "name" }) {
                                      if (quad.containsKey(attr)) {
                                          final Map<String, Object> qa = (Map<String, Object>) quad
                                                  .get(attr);
                                          if (qa != null && "blank node".equals(qa.get("type"))
                                                  && ((String) qa.get("value"))
                                                          .indexOf("_:c14n") != 0) {
                                              qa.put("value",
                                                      namer.getName((String) qa.get(("value"))));
                                          }
                                      }
                                  }
                                  normalized.add(toNQuad((RDFDataset.Quad) quad,
                                          quad.containsKey("name") && quad.get("name") != null
                                                  ? (String) ((Map<String, Object>) quad.get("name"))
                                                          .get("value")
                                                  : null));
                              }
  
                              // sort normalized output
                              Collections.sort(normalized);
  
                              // handle output format
                              if (options.format != null) {
                                  if (JsonLdConsts.APPLICATION_NQUADS.equals(options.format)) {
                                      final StringBuilder rval = new StringBuilder();
                                      for (final String n : normalized) {
                                          rval.append(n);
                                      }
                                      return rval.toString();
                                  } else {
                                      throw new JsonLdError(JsonLdError.Error.UNKNOWN_FORMAT,
                                              options.format);
                                  }
                              }
                              final StringBuilder rval = new StringBuilder();
                              for (final String n : normalized) {
                                  rval.append(n);
                              }
                              return parseNQuads(rval.toString());
                          }
  
                          // name each group member
                          final List<String> group = duplicates.get(hashes.get(pgi));
                          final List<HashResult> results = new ArrayList<HashResult>();
                          for (int n = 0;; n++) {
                              if (n == group.size()) {
                                  // name bnodes in hash order
                                  Collections.sort(results, new Comparator<HashResult>() {
                                      @Override
                                      public int compare(HashResult a, HashResult b) {
                                          final int res = a.hash.compareTo(b.hash);
                                          return res;
                                      }
                                  });
                                  for (final HashResult r : results) {
                                      // name all bnodes in path namer in
                                      // key-entry order
                                      // Note: key-order is preserved in
                                      // javascript
                                      for (final String key : r.pathNamer.existing().keySet()) {
                                          namer.getName(key);
                                      }
                                  }
                                  // processGroup(i+1);
                                  break;
                              } else {
                                  // skip already-named bnodes
                                  final String bnode = group.get(n);
                                  if (namer.isNamed(bnode)) {
                                      continue;
                                  }
  
                                  // hash bnode paths
                                  final UniqueNamer pathNamer = new UniqueNamer("_:b");
                                  pathNamer.getName(bnode);
  
                                  final HashResult result = hashPaths(bnode, bnodes, namer,
                                          pathNamer);
                                  results.add(result);
                              }
                          }
                      }
                  }
              }
  
              // hash unnamed bnode
              final String bnode = unnamed.get(hui);
              final String hash = hashQuads(bnode, bnodes, namer);
  
              // store hash as unique or a duplicate
              if (duplicates.containsKey(hash)) {
                  duplicates.get(hash).add(bnode);
                  nextUnnamed.add(bnode);
              } else if (unique.containsKey(hash)) {
                  final List<String> tmp = new ArrayList<String>();
                  tmp.add(unique.get(hash));
                  tmp.add(bnode);
                  duplicates.put(hash, tmp);
                  nextUnnamed.add(unique.get(hash));
                  nextUnnamed.add(bnode);
                  unique.remove(hash);
              } else {
                  unique.put(hash, bnode);
              }
          }
      }