private SubTreeDescriptor buildTree(
      final int queryPlanDepth,
      final int currentDepth,
      final DbIterator queryPlan,
      final int currentStartPosition,
      final int parentUpperBarStartShift) {
    if (queryPlan == null) return null;

    int adjustDepth = currentDepth == 0 ? -1 : 0;
    SubTreeDescriptor thisNode = new SubTreeDescriptor(null, null);

    if (queryPlan instanceof SeqScan) {
      SeqScan s = (SeqScan) queryPlan;
      String tableName = s.getTableName();
      String alias = s.getAlias();
      // TupleDesc td = s.getTupleDesc();
      if (!tableName.equals(alias)) alias = " " + alias;
      else alias = "";
      thisNode.text = String.format("%1$s(%2$s)", SCAN, tableName + alias);
      if (SCAN.length() / 2 < parentUpperBarStartShift) {
        thisNode.upBarPosition = currentStartPosition + parentUpperBarStartShift;
        thisNode.textStartPosition = thisNode.upBarPosition - SCAN.length() / 2;
      } else {
        thisNode.upBarPosition = currentStartPosition + SCAN.length() / 2;
        thisNode.textStartPosition = currentStartPosition;
      }
      thisNode.width = thisNode.textStartPosition - currentStartPosition + thisNode.text.length();
      int embedHeight = (queryPlanDepth - currentDepth) / 2 - 1;
      thisNode.height = currentDepth + 2 * embedHeight;
      int currentHeight = thisNode.height;
      SubTreeDescriptor parentNode = thisNode;
      for (int i = 0; i < embedHeight; i++) {
        parentNode = new SubTreeDescriptor(parentNode, null);
        parentNode.text = "|";
        parentNode.upBarPosition = thisNode.upBarPosition;
        parentNode.width = thisNode.width;
        parentNode.height = currentHeight - 2;
        parentNode.textStartPosition = thisNode.upBarPosition;
        currentHeight -= 2;
      }
      thisNode = parentNode;
    } else {

      Operator plan = (Operator) queryPlan;
      DbIterator[] children = plan.getChildren();

      if (plan instanceof Join) {
        Join j = (Join) plan;
        TupleDesc td = j.getTupleDesc();
        JoinPredicate jp = j.getJoinPredicate();
        String field1 = td.getFieldName(jp.getField1());
        String field2 = td.getFieldName(jp.getField2() + children[0].getTupleDesc().numFields());
        thisNode.text =
            String.format(
                "%1$s(%2$s),card:%3$d",
                JOIN, field1 + jp.getOperator() + field2, j.getEstimatedCardinality());
        int upBarShift = parentUpperBarStartShift;
        if (JOIN.length() / 2 > parentUpperBarStartShift) upBarShift = JOIN.length() / 2;

        SubTreeDescriptor left =
            buildTree(
                queryPlanDepth,
                currentDepth + adjustDepth + 3,
                children[0],
                currentStartPosition,
                upBarShift);
        SubTreeDescriptor right =
            buildTree(
                queryPlanDepth,
                currentDepth + adjustDepth + 3,
                children[1],
                currentStartPosition + left.width + SPACE.length(),
                0);
        thisNode.upBarPosition = (left.upBarPosition + right.upBarPosition) / 2;
        thisNode.textStartPosition = thisNode.upBarPosition - JOIN.length() / 2;
        thisNode.width =
            Math.max(
                left.width + right.width + SPACE.length(),
                thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
        thisNode.leftChild = left;
        thisNode.rightChild = right;
        thisNode.height = currentDepth;
      } else if (plan instanceof HashEquiJoin) {
        HashEquiJoin j = (HashEquiJoin) plan;
        JoinPredicate jp = j.getJoinPredicate();
        TupleDesc td = j.getTupleDesc();
        String field1 = td.getFieldName(jp.getField1());
        String field2 = td.getFieldName(jp.getField2() + children[0].getTupleDesc().numFields());
        thisNode.text =
            String.format(
                "%1$s(%2$s),card:%3$d",
                HASH_JOIN, field1 + jp.getOperator() + field2, j.getEstimatedCardinality());
        int upBarShift = parentUpperBarStartShift;
        if (HASH_JOIN.length() / 2 > parentUpperBarStartShift) upBarShift = HASH_JOIN.length() / 2;
        SubTreeDescriptor left =
            buildTree(
                queryPlanDepth,
                currentDepth + 3 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        SubTreeDescriptor right =
            buildTree(
                queryPlanDepth,
                currentDepth + 3 + adjustDepth,
                children[1],
                currentStartPosition + left.width + SPACE.length(),
                0);
        thisNode.upBarPosition = (left.upBarPosition + right.upBarPosition) / 2;
        thisNode.textStartPosition = thisNode.upBarPosition - HASH_JOIN.length() / 2;
        thisNode.width =
            Math.max(
                left.width + right.width + SPACE.length(),
                thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
        thisNode.leftChild = left;
        thisNode.rightChild = right;
        thisNode.height = currentDepth;
      } else if (plan instanceof Aggregate) {
        Aggregate a = (Aggregate) plan;
        int upBarShift = parentUpperBarStartShift;
        String alignTxt;
        TupleDesc td = a.getTupleDesc();
        int gfield = a.groupField();

        if (gfield == Aggregator.NO_GROUPING) {
          thisNode.text =
              String.format(
                  "%1$s(%2$s),card:%3$d",
                  a.aggregateOp(), a.aggregateFieldName(), a.getEstimatedCardinality());
          alignTxt = td.getFieldName(00);
        } else {
          thisNode.text =
              String.format(
                  "%1$s(%2$s), %3$s(%4$s),card:%5$d",
                  GROUPBY,
                  a.groupFieldName(),
                  a.aggregateOp(),
                  a.aggregateFieldName(),
                  a.getEstimatedCardinality());
          alignTxt = GROUPBY;
        }
        if (alignTxt.length() / 2 > parentUpperBarStartShift) upBarShift = alignTxt.length() / 2;

        SubTreeDescriptor child =
            buildTree(
                queryPlanDepth,
                currentDepth + 2 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        thisNode.upBarPosition = child.upBarPosition;
        thisNode.textStartPosition = thisNode.upBarPosition - alignTxt.length() / 2;
        thisNode.width =
            Math.max(
                child.width,
                thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
        thisNode.leftChild = child;
        thisNode.height = currentDepth;
      } else if (plan instanceof Filter) {
        Filter f = (Filter) plan;
        Predicate p = f.getPredicate();
        thisNode.text =
            String.format(
                "%1$s(%2$s),card:%3$d",
                SELECT,
                children[0].getTupleDesc().getFieldName(p.getField()) + p.getOp() + p.getOperand(),
                f.getEstimatedCardinality());
        int upBarShift = parentUpperBarStartShift;
        if (SELECT.length() / 2 > parentUpperBarStartShift) upBarShift = SELECT.length() / 2;
        SubTreeDescriptor child =
            buildTree(
                queryPlanDepth,
                currentDepth + 2 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        thisNode.upBarPosition = child.upBarPosition;
        thisNode.textStartPosition = thisNode.upBarPosition - SELECT.length() / 2;
        thisNode.width =
            Math.max(
                child.width,
                thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
        thisNode.leftChild = child;
        thisNode.height = currentDepth;
      } else if (plan instanceof OrderBy) {
        OrderBy o = (OrderBy) plan;
        thisNode.text =
            String.format(
                "%1$s(%2$s),card:%3$d",
                ORDERBY,
                children[0].getTupleDesc().getFieldName(o.getOrderByField()),
                o.getEstimatedCardinality());
        int upBarShift = parentUpperBarStartShift;
        if (ORDERBY.length() / 2 > parentUpperBarStartShift) upBarShift = ORDERBY.length() / 2;
        SubTreeDescriptor child =
            buildTree(
                queryPlanDepth,
                currentDepth + 2 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        thisNode.upBarPosition = child.upBarPosition;
        thisNode.textStartPosition = thisNode.upBarPosition - ORDERBY.length() / 2;
        thisNode.width =
            Math.max(
                child.width,
                thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
        thisNode.leftChild = child;
        thisNode.height = currentDepth;
      } else if (plan instanceof Project) {
        Project p = (Project) plan;
        String fields = "";
        Iterator<TDItem> it = p.getTupleDesc().iterator();
        while (it.hasNext()) fields += it.next().fieldName + ",";
        fields = fields.substring(0, fields.length() - 1);
        thisNode.text =
            String.format("%1$s(%2$s),card:%3$d", PROJECT, fields, p.getEstimatedCardinality());
        int upBarShift = parentUpperBarStartShift;
        if (PROJECT.length() / 2 > parentUpperBarStartShift) upBarShift = PROJECT.length() / 2;
        SubTreeDescriptor child =
            buildTree(
                queryPlanDepth,
                currentDepth + 2 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        thisNode.upBarPosition = child.upBarPosition;
        thisNode.textStartPosition = thisNode.upBarPosition - PROJECT.length() / 2;
        thisNode.width =
            Math.max(
                child.width,
                thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
        thisNode.leftChild = child;
        thisNode.height = currentDepth;
      } else if (plan.getClass()
          .getSuperclass()
          .getSuperclass()
          .getSimpleName()
          .equals("Exchange")) {
        String name = "Exchange";
        int card = 0;
        try {
          name = (String) plan.getClass().getMethod("getName").invoke(plan);
          card = (Integer) plan.getClass().getMethod("getEstimatedCardinality").invoke(plan);
        } catch (Exception e) {
          e.printStackTrace();
        }

        thisNode.text = String.format("%1$s,card:%2$d", name, card);
        int upBarShift = parentUpperBarStartShift;
        if (name.length() / 2 > parentUpperBarStartShift) upBarShift = name.length() / 2;
        SubTreeDescriptor child =
            buildTree(
                queryPlanDepth,
                currentDepth + 2 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        if (child == null) {
          thisNode.upBarPosition = upBarShift;
          thisNode.textStartPosition = thisNode.upBarPosition - name.length() / 2;
          thisNode.width =
              thisNode.textStartPosition + thisNode.text.length() - currentStartPosition;
        } else {
          thisNode.upBarPosition = child.upBarPosition;
          thisNode.textStartPosition = thisNode.upBarPosition - name.length() / 2;
          thisNode.width =
              Math.max(
                  child.width,
                  thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
          thisNode.leftChild = child;
        }
        thisNode.height = currentDepth;
      } else if (plan.getClass().getName().equals("simpledb.Rename")) {
        String newName = null;
        int fieldIdx = 0;
        try {
          newName = (String) plan.getClass().getMethod("newName", (Class<?>[]) null).invoke(plan);
          fieldIdx =
              (Integer) plan.getClass().getMethod("renamedField", (Class<?>[]) null).invoke(plan);
        } catch (Exception e) {
          e.printStackTrace();
        }
        String oldName = plan.getChildren()[0].getTupleDesc().getFieldName(fieldIdx);
        thisNode.text =
            String.format(
                "%1$s,%2$s->%3$s,card:%4$d",
                RENAME, oldName, newName, plan.getEstimatedCardinality());
        int upBarShift = parentUpperBarStartShift;
        if (RENAME.length() / 2 > parentUpperBarStartShift) upBarShift = RENAME.length() / 2;
        SubTreeDescriptor child =
            buildTree(
                queryPlanDepth,
                currentDepth + 2 + adjustDepth,
                children[0],
                currentStartPosition,
                upBarShift);
        if (child == null) {
          thisNode.upBarPosition = upBarShift;
          thisNode.textStartPosition = thisNode.upBarPosition - RENAME.length() / 2;
          thisNode.width =
              thisNode.textStartPosition + thisNode.text.length() - currentStartPosition;
        } else {
          thisNode.upBarPosition = child.upBarPosition;
          thisNode.textStartPosition = thisNode.upBarPosition - RENAME.length() / 2;
          thisNode.width =
              Math.max(
                  child.width,
                  thisNode.textStartPosition + thisNode.text.length() - currentStartPosition);
          thisNode.leftChild = child;
        }
        thisNode.height = currentDepth;
      }
    }
    return thisNode;
  }