private ObjectNode convert(Map<String, Integer> map) {
   ObjectNode res = JsonUtils.createObjectNode();
   for (Entry<String, Integer> entry : map.entrySet()) {
     res.put(entry.getKey(), entry.getValue());
   }
   return res;
 }
 /** Renders a JSON report containing the collected metrics. */
 public ObjectNode report() {
   ObjectNode res = JsonUtils.createObjectNode();
   res.put("instructions", convert(instructions));
   res.put("formatters", convert(formatters));
   res.put("predicates", convert(predicates));
   res.put("variables", currentNode);
   res.put("textBytes", textBytes);
   // NOTE: this is temporary - phensley
   res.put("ifInstructions", convert(ifVariants));
   return res;
 }
 /** Pushes one variable scope level. */
 private void pushSection(String name) {
   JsonNode node = currentNode.path(name);
   ObjectNode obj = null;
   if (node.isObject()) {
     obj = (ObjectNode) node;
   } else {
     obj = JsonUtils.createObjectNode();
     currentNode.put(name, obj);
   }
   variables.push(currentNode);
   currentNode = obj;
 }
  /** Holds a set of metrics on all instructions referenced in a template. */
  public static class References {

    private Deque<ObjectNode> variables = new ArrayDeque<>();

    private ObjectNode currentNode = JsonUtils.createObjectNode();

    private Map<String, Integer> instructions = new HashMap<>();

    private Map<String, Integer> formatters = new HashMap<>();

    private Map<String, Integer> predicates = new HashMap<>();

    private List<String> ifVariants = new ArrayList<>();

    private int textBytes;

    /** Renders a JSON report containing the collected metrics. */
    public ObjectNode report() {
      ObjectNode res = JsonUtils.createObjectNode();
      res.put("instructions", convert(instructions));
      res.put("formatters", convert(formatters));
      res.put("predicates", convert(predicates));
      res.put("variables", currentNode);
      res.put("textBytes", textBytes);
      // NOTE: this is temporary - phensley
      res.put("ifInstructions", convert(ifVariants));
      return res;
    }

    private ObjectNode convert(Map<String, Integer> map) {
      ObjectNode res = JsonUtils.createObjectNode();
      for (Entry<String, Integer> entry : map.entrySet()) {
        res.put(entry.getKey(), entry.getValue());
      }
      return res;
    }

    private ArrayNode convert(List<String> list) {
      ArrayNode res = JsonUtils.createArrayNode();
      for (String elem : list) {
        res.add(elem);
      }
      return res;
    }

    private void increment(Instruction inst) {
      if (inst != null) {
        increment(instructions, inst.getType().name());
      }
    }

    private void increment(Predicate predicate) {
      if (predicate != null) {
        increment(predicates, predicate.getIdentifier());
      }
    }

    private void increment(Formatter formatter) {
      if (formatter != null) {
        increment(formatters, formatter.getIdentifier());
      }
    }

    private void increment(Map<String, Integer> counter, String key) {
      Integer value = counter.get(key);
      if (value == null) {
        value = 0;
      }
      counter.put(key, value + 1);
    }

    /** Adds a variable to the current scope. */
    private void addVariable(String name) {
      JsonNode node = currentNode.path(name);
      if (node.isMissingNode()) {
        currentNode.put(name, NullNode.getInstance());
      }
    }

    // NOTE: This is temporary, to assess the .if instruction variants out there and
    // assess the impact of migration. Will remove this once data is gathered. - phensley
    private void addIfInstruction(BlockInstruction inst) {
      StringBuilder buf = new StringBuilder();
      if (inst instanceof IfInst) {
        ReprEmitter.emit((IfInst) inst, buf, false);
      } else {
        ReprEmitter.emit((IfPredicateInst) inst, buf, false);
      }
      ifVariants.add(buf.toString());
    }

    /** Pushes one variable scope level. */
    private void pushSection(String name) {
      JsonNode node = currentNode.path(name);
      ObjectNode obj = null;
      if (node.isObject()) {
        obj = (ObjectNode) node;
      } else {
        obj = JsonUtils.createObjectNode();
        currentNode.put(name, obj);
      }
      variables.push(currentNode);
      currentNode = obj;
    }

    /** Pops one variable scope level. */
    private void popSection() {
      currentNode = variables.pop();
    }
  }