@Override
  public void buildEdgeRelation(CategoryCache categoryCache) throws WikitException {
    if (!categoryCache.hasDone()) {
      throw new WikitException("Please create category cache first.");
    }
    String[] skippedCategories = new String[] {"跨学科领域", "总类", "词汇列表"};
    Set<Integer> skippedCatIds = new HashSet<>();
    for (String c : skippedCategories) {
      skippedCatIds.add(categoryCache.getIdByName(c));
    }

    ProgressCounter counter = new ProgressCounter();
    try {
      String root = conf.getWikiRootCategoryName();
      int id = categoryCache.getIdByName(root);

      this.saveNameIdMapping(root, id);
      this.rootId = id;
      this.saveDepth(id, 0);

      Queue<Integer> queue = new LinkedList<>();
      queue.add(id);
      while (!queue.isEmpty()) {
        int pid = queue.poll();
        int depth = this.getDepth(pid);
        Set<Integer> childIds = categoryCache.getChildIds(pid);

        for (Integer childId : childIds) {
          if (skippedCatIds.contains(childId)) {
            continue;
          }

          int childDepth = categoryCache.getDepth(childId, -1);
          if (childDepth != (depth + 1)) {
            continue; // skip
          }

          if (!idExist(childId)) {
            String name = categoryCache.getNameById(childId);
            this.saveNameIdMapping(name, childId);
            this.saveDepth(childId, depth + 1);

            queue.add(childId);
          }
          this.saveParents(childId, pid);
          this.saveChildren(pid, childId);
        }

        counter.increment();
      }
      this.finishNameIdMapping();
      this.edgeRelationCreated();
    } catch (MissedException e) {
      LOG.error(e.toString());
      throw new WikitException(e);
    }

    counter.done();
    LOG.info("Edge relation has created.");
  }
  private void saveParents(int catId, Collection<String> parents) {
    byte[] key = (prefix + "id2pids").getBytes(ENCODING);
    byte[] hkey = NumberUtils.int2Bytes(catId);

    byte[] value = jedis.hget(key, hkey);
    Set<Integer> ids = NumberUtils.bytes2IntSet(value);

    boolean changed = false;
    for (String p : parents) {
      if (nameExist(p)) {
        try {
          ids.add(getIdByName(p));
          changed = true;
        } catch (MissedException e) {
          LOG.warn(e.toString());
        }
      }
    }
    if (changed) jedis.hset(key, hkey, NumberUtils.intSet2Bytes(ids));
  }
  private void saveChildren(int catId, String... children) {
    byte[] key = (prefix + "id2cids").getBytes(ENCODING);
    byte[] hkey = NumberUtils.int2Bytes(catId);

    byte[] value = jedis.hget(key, hkey);
    Set<Integer> ids = NumberUtils.bytes2IntSet(value);

    boolean changed = false;
    for (String c : children) {
      if (nameExist(c)) {
        try {
          ids.add(getIdByName(c));
          changed = true;
        } catch (MissedException e) {
          e.printStackTrace();
        }
      }
    }
    if (changed) jedis.hset(key, hkey, NumberUtils.intSet2Bytes(ids));
  }