@Override
  public void visit(Matches entity) {
    Expression object = entity.getObject();
    if (isLearning() && EntityUtils.isResolver(object)) {
      putLearntEntity(object, getBindings().wGet("evaluatedSubject"));

      setResultVisitor(GenericTraversalFactory.instance.identity());
    } else super.visit(entity);
  }
  @Override
  public void visit(HasType entity) {
    Name edName = entity.getDescriptorName();
    if (isLearning() && EntityUtils.isResolver(edName)) {
      EntityDescriptor<?> ed = getBindings().wGet("evaluatedSubject").wGetEntityDescriptor();
      Name learntEdName = TestsEntityFactory.instance.createName(ed.getName());

      putLearntEntity(edName, learntEdName);
      setResultVisitor(GenericTraversalFactory.instance.identity());
    } else super.visit(entity);
  }
  @Override
  public void visit(HasKind entity) {
    Kind kind = entity.getKind();
    if (isLearning() && EntityUtils.isResolver(kind)) {

      EntityKinds entityKind = getBindings().wGet("evaluatedSubject").wGetEntityKind();
      Value entityKindValue = KindEnum.instance.valueOf(entityKind.name());
      Kind learntKind = TestsEntityFactory.instance.createKind(entityKindValue);

      putLearntEntity(kind, learntKind);
      setResultVisitor(GenericTraversalFactory.instance.identity());
    } else super.visit(entity);
  }
  @Override
  public void visit(Throws entity) {
    ThrowableType throwableType = entity.getThrowableType();
    if (isLearning()
        && EntityUtils.isResolver(throwableType)
        && getBindings().wIsSet("thrownException")) {
      Exception throwable = (Exception) getBindings().wGetValue("thrownException");
      ThrowableType learntThrowable =
          TestsEntityFactory.instance.createThrowableType(throwable.getClass().getName());
      putLearntEntity(throwableType, learntThrowable);

      setResultVisitor(GenericTraversalFactory.instance.identity());
    } else super.visit(entity);
  }
  @Override
  public void visit(IsAssignableTo entity) {
    Name edName = entity.getDescriptorName();
    if (isLearning() && EntityUtils.isResolver(edName)) {
      IEntity subject = getBindings().wGet("evaluatedSubject");
      EntityDescriptor<?> ed =
          EntityUtils.hasParent(subject)
              ? subject.wGetParent().wGetEntityDescriptor(subject)
              : subject.wGetEntityDescriptor();
      Name learntEdName = TestsEntityFactory.instance.createName(ed.getName());

      putLearntEntity(edName, learntEdName);
      setResultVisitor(GenericTraversalFactory.instance.identity());
    } else super.visit(entity);
  }
  @Override
  public void visit(TestSuite entity) {
    if (isLearning()) {
      Map<IEntity, List<IEntity>> learntMap = getLearntMap();

      IEntity result = null;
      for (int cycle = 1; cycle <= learnCycles(); cycle++) {
        printWriter().printf("*** Learning cycle %d ***\n\n", cycle);
        ITransactionScope resettableScope = BindingManagerFactory.instance.createTransactionScope();
        getBindings().wEnterScope(resettableScope);
        try {
          getBindings().wDefValue("learnCycle", cycle);
          super.visit(entity);
          result = getBindings().getResult();
        } finally {
          resettableScope.rollback();
          getBindings().wExitScope();
        }
      }
      getBindings().setResult(result);

      FilterFamily filterFamily = getFilterFamily(entity);
      FilterRules filterRules = filterFamily.getFilterRules();
      FreshNameGenerator fnGen = new FreshNameGenerator();
      for (IEntity name :
          BehaviorUtils.compileAndLazyEvaluate(createFindAllFilterRuleNamesQuery(), filterFamily))
        fnGen.addBoundName(name.wStringValue());

      for (IEntity adapter : learntMap.keySet()) {
        List<IEntity> learntEntities = learntMap.get(adapter);

        IEntity value = learntEntities.get(0);
        if (learntEntities.size() > 1 && EntityUtils.isData(value)) {
          for (IEntity learntEntity : learntEntities) if (!learntEntity.wEquals(value)) continue;
        } else if (learntEntities.size() > 1) {
          for (IEntity learntEntity2 : learntEntities)
            if (EntityUtils.isData(learntEntity2)) continue;

          // generate filter rule
          FilterRule filterRule = TestsHelpers.createFilterRule(learntEntities);
          if (EntityUtils.isNotResolver(filterRule)) {
            String filterName;
            IEntity filterBody;
            if ((filterBody = Matcher.find(filterRule.getBody(), filterRules, false)) != null) {
              // try to reuse a generated filter
              filterName = ((FilterRule) filterBody.wGetParent()).getName().getValue();
            } else {
              // add the filter rule to the filter family
              filterName = fnGen.nextFreshName(GENERATED_FILTER_NAME);
              ITransactionScope resettableScope =
                  BindingManagerFactory.instance.createTransactionScope();
              getBindings().wEnterScope(resettableScope);
              try {
                getBindings().wDefValue("filterName", filterName);
                Matcher.substitute(filterRule, getBindings(), false);
                filterRules.wAdd(filterRule);
              } finally {
                resettableScope.rollback();
                getBindings().wExitScope();
              }
            }

            // wrap SubjectStatement with a UsingFilter
            ITransactionScope resettableScope =
                BindingManagerFactory.instance.createTransactionScope();
            getBindings().wEnterScope(resettableScope);
            try {
              SubjectStatement statement =
                  BehaviorUtils.evaluateFirstResult(
                      createFindAncestorSubjectStatement(), adapter, getBindings());
              UsingFilter usingFilter = createUsingFilter(filterName);
              statement.wGetParent().wSet(statement, usingFilter);
              usingFilter.setSubjectStatement(statement);
            } finally {
              resettableScope.rollback();
              getBindings().wExitScope();
            }
          }
        }

        TestsHelpers.replace(adapter, value);
      }
      // add the newly generated family
      if (!EntityUtils.hasParent(filterFamily) && !filterRules.wIsEmpty())
        entity.getFilterFamilies().wAdd(filterFamily);
    } else super.visit(entity);
  }