docs: private static HashResult hashPaths(String id, Map<String, Object> bnodes, UniqueNamer namer,
              UniqueNamer pathNamer) {
          try {
              // create SHA-1 digest
              final MessageDigest md = MessageDigest.getInstance("SHA-1");
  
              final Map<String, List<String>> groups = new LinkedHashMap<String, List<String>>();
              List<String> groupHashes;
              final List<Object> quads = (List<Object>) ((Map<String, Object>) bnodes.get(id))
                      .get("quads");
  
              for (int hpi = 0;; hpi++) {
                  if (hpi == quads.size()) {
                      // done , hash groups
                      groupHashes = new ArrayList<String>(groups.keySet());
                      Collections.sort(groupHashes);
                      for (int hgi = 0;; hgi++) {
                          if (hgi == groupHashes.size()) {
                              final HashResult res = new HashResult();
                              res.hash = encodeHex(md.digest());
                              res.pathNamer = pathNamer;
                              return res;
                          }
  
                          // digest group hash
                          final String groupHash = groupHashes.get(hgi);
                          md.update(groupHash.getBytes("UTF-8"));
  
                          // choose a path and namer from the permutations
                          String chosenPath = null;
                          UniqueNamer chosenNamer = null;
                          final Permutator permutator = new Permutator(groups.get(groupHash));
                          while (true) {
                              Boolean contPermutation = false;
                              Boolean breakOut = false;
                              final List<String> permutation = permutator.next();
                              UniqueNamer pathNamerCopy = pathNamer.clone();
  
                              // build adjacent path
                              String path = "";
                              final List<String> recurse = new ArrayList<String>();
                              for (final String bnode : permutation) {
                                  // use canonical name if available
                                  if (namer.isNamed(bnode)) {
                                      path += namer.getName(bnode);
                                  } else {
                                      // recurse if bnode isn't named in the path
                                      // yet
                                      if (!pathNamerCopy.isNamed(bnode)) {
                                          recurse.add(bnode);
                                      }
                                      path += pathNamerCopy.getName(bnode);
                                  }
  
                                  // skip permutation if path is already >= chosen
                                  // path
                                  if (chosenPath != null && path.length() >= chosenPath.length()
                                          && path.compareTo(chosenPath) > 0) {
                                      // return nextPermutation(true);
                                      if (permutator.hasNext()) {
                                          contPermutation = true;
                                      } else {
                                          // digest chosen path and update namer
                                          md.update(chosenPath.getBytes("UTF-8"));
                                          pathNamer = chosenNamer;
                                          // hash the nextGroup
                                          breakOut = true;
                                      }
                                      break;
                                  }
                              }
  
                              // if we should do the next permutation
                              if (contPermutation) {
                                  continue;
                              }
                              // if we should stop processing this group
                              if (breakOut) {
                                  break;
                              }
  
                              // does the next recursion
                              for (int nrn = 0;; nrn++) {
                                  if (nrn == recurse.size()) {
                                      // return nextPermutation(false);
                                      if (chosenPath == null || path.compareTo(chosenPath) < 0) {
                                          chosenPath = path;
                                          chosenNamer = pathNamerCopy;
                                      }
                                      if (!permutator.hasNext()) {
                                          // digest chosen path and update namer
                                          md.update(chosenPath.getBytes("UTF-8"));
                                          pathNamer = chosenNamer;
                                          // hash the nextGroup
                                          breakOut = true;
                                      }
                                      break;
                                  }
  
                                  // do recursion
                                  final String bnode = recurse.get(nrn);
                                  final HashResult result = hashPaths(bnode, bnodes, namer,
                                          pathNamerCopy);
                                  path += pathNamerCopy.getName(bnode) + "<" + result.hash + ">";
                                  pathNamerCopy = result.pathNamer;
  
                                  // skip permutation if path is already >= chosen
                                  // path
                                  if (chosenPath != null && path.length() >= chosenPath.length()
                                          && path.compareTo(chosenPath) > 0) {
                                      // return nextPermutation(true);
                                      if (!permutator.hasNext()) {
                                          // digest chosen path and update namer
                                          md.update(chosenPath.getBytes("UTF-8"));
                                          pathNamer = chosenNamer;
                                          // hash the nextGroup
                                          breakOut = true;
                                      }
                                      break;
                                  }
                                  // do next recursion
                              }
  
                              // if we should stop processing this group
                              if (breakOut) {
                                  break;
                              }
                          }
                      }
                  }
  
                  // get adjacent bnode
                  final Map<String, Object> quad = (Map<String, Object>) quads.get(hpi);
                  String bnode = getAdjacentBlankNodeName((Map<String, Object>) quad.get("subject"),
                          id);
                  String direction = null;
                  if (bnode != null) {
                      // normal property
                      direction = "p";
                  } else {
                      bnode = getAdjacentBlankNodeName((Map<String, Object>) quad.get("object"), id);
                      if (bnode != null) {
                          // reverse property
                          direction = "r";
                      }
                  }
  
                  if (bnode != null) {
                      // get bnode name (try canonical, path, then hash)
                      String name;
                      if (namer.isNamed(bnode)) {
                          name = namer.getName(bnode);
                      } else if (pathNamer.isNamed(bnode)) {
                          name = pathNamer.getName(bnode);
                      } else {
                          name = hashQuads(bnode, bnodes, namer);
                      }
  
                      // hash direction, property, end bnode name/hash
                      final MessageDigest md1 = MessageDigest.getInstance("SHA-1");
                      // String toHash = direction + (String) ((Map<String,
                      // Object>) quad.get("predicate")).get("value") + name;
                      md1.update(direction.getBytes("UTF-8"));
                      md1.update(((String) ((Map<String, Object>) quad.get("predicate")).get("value"))
                              .getBytes("UTF-8"));
                      md1.update(name.getBytes("UTF-8"));
                      final String groupHash = encodeHex(md1.digest());
                      if (groups.containsKey(groupHash)) {
                          groups.get(groupHash).add(bnode);
                      } else {
                          final List<String> tmp = new ArrayList<String>();
                          tmp.add(bnode);
                          groups.put(groupHash, tmp);
                      }
                  }
              }
          } catch (final NoSuchAlgorithmException e) {
              // TODO: i don't expect that SHA-1 is even NOT going to be
              // available?
              // look into this further
              throw new RuntimeException(e);
          } catch (final UnsupportedEncodingException e) {
              // TODO: i don't expect that UTF-8 is ever not going to be available
              // either
              throw new RuntimeException(e);
          }
      }