public static <T> T visit(final Schema start, final SchemaVisitor<T> visitor) {
    // Set of Visited Schemas
    IdentityHashMap<Schema, Schema> visited = new IdentityHashMap<>();
    // Stack that contains the Schemas to process and afterVisitNonTerminal
    // functions.
    // Deque<Either<Schema, Supplier<SchemaVisitorAction>>>
    // Using Either<...> has a cost we want to avoid...
    Deque<Object> dq = new ArrayDeque<>();
    dq.push(start);
    Object current;
    while ((current = dq.poll()) != null) {
      if (current instanceof Supplier) {
        // We are executing a non-terminal post visit.
        SchemaVisitor.SchemaVisitorAction action = ((Supplier<SchemaVisitor.SchemaVisitorAction>) current).get();
        switch (action) {
        case CONTINUE:
          break;
        case SKIP_SIBLINGS:
          while (dq.peek() instanceof Schema) {
            dq.remove();
          }
          break;
        case TERMINATE:
          return visitor.get();
        case SKIP_SUBTREE:
        default:
          throw new UnsupportedOperationException("Invalid action " + action);
        }
      } else {
        Schema schema = (Schema) current;
        boolean terminate;
        if (visited.containsKey(schema)) {
          terminate = visitTerminal(visitor, schema, dq);
        } else {
          Schema.Type type = schema.getType();
          switch (type) {
          case ARRAY:
            terminate = visitNonTerminal(visitor, schema, dq, Collections.singleton(schema.getElementType()));
            visited.put(schema, schema);
            break;
          case RECORD:
            terminate = visitNonTerminal(visitor, schema, dq, () -> schema.getFields().stream().map(Field::schema)
                .collect(Collectors.toCollection(ArrayDeque::new)).descendingIterator());
            visited.put(schema, schema);
            break;
          case UNION:
            terminate = visitNonTerminal(visitor, schema, dq, schema.getTypes());
            visited.put(schema, schema);
            break;
          case MAP:
            terminate = visitNonTerminal(visitor, schema, dq, Collections.singleton(schema.getValueType()));
            visited.put(schema, schema);
            break;
          default:
            terminate = visitTerminal(visitor, schema, dq);
            break;
          }
        }
        if (terminate) {
          return visitor.get();
        }
      }
    }
    return visitor.get();
  }
@Test
  public void testVisit3() {
    String s3 = "{\"type\": \"record\", \"name\": \"ss1\", \"fields\": [" + "{\"name\": \"f1\", \"type\": \"int\"}"
        + "]}";
    Assert.assertEquals("ss1.", Schemas.visit(new Schema.Parser().parse(s3), new TestVisitor()));

  }