protected Expression processPropertyTracedownTooneOptimized(
      Attribute<?, ?> attr, Expression builder, List<Parameter> parameters) {
    ConditionQuery subQuery = getTraceDownQuery();

    Expression finalExp = null;
    for (PropertyCondition subPc : subQuery.getConditions()) {
      if (subPc.isReturnSimpleExpression()) {
        Expression subExp = subPc.processProperty(builder.get(getProp()), parameters);
        if (finalExp == null) {
          finalExp = subExp;
        } else {
          finalExp = finalExp.and(subExp);
        }
      } else {
        // low-performance query since an extra layer of exists added
        // but this is the only way to make it work.
        // this situation is rare. So we may do not need to optimize it
        Expression subBuilder = new ExpressionBuilder();
        Expression subExp = subPc.processProperty(subBuilder, parameters);
        ReportQuery childQuery = formRelationReportQuery(attr, subBuilder);
        childQuery.retrievePrimaryKeys();
        childQuery.setSelectionCriteria(subBuilder.equal(builder.get(getProp())).and(subExp));
        if (finalExp == null) {
          finalExp = builder.exists(childQuery);
        } else {
          finalExp = finalExp.and(builder.exists(childQuery));
        }
      }
    }
    return finalExp;
  }
  protected Expression processPropertyNotLike(
      Attribute<?, ?> attr, Expression builder, List<Parameter> parameters) {

    ConditionQueryParameter p;
    // convenient expression, only used by simple case
    Expression exp = null;

    p = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
    parameters.add(p);
    if (attr.isCollection()) {
      if (isAllColllectionMode()) {
        ReportQuery subQuery = formReportQueryForDirectCollection(attr, builder);
        if (isIgnoreCase()) {
          subQuery.setSelectionCriteria(
              builder.anyOf(getProp()).likeIgnoreCase(builder.getParameter(p.getParamName())));
        } else {
          subQuery.setSelectionCriteria(
              builder.anyOf(getProp()).like(builder.getParameter(p.getParamName())));
        }

        exp = builder.notExists(subQuery);
      } else if (isNoneColllectionMode()) {
        ReportQuery subQuery = formReportQueryForDirectCollection(attr, builder);
        if (isIgnoreCase()) {
          subQuery.setSelectionCriteria(
              builder
                  .anyOf(getProp())
                  .toUpperCase()
                  .notLike(builder.getParameter(p.getParamName())));
        } else {
          subQuery.setSelectionCriteria(
              builder.anyOf(getProp()).notLike(builder.getParameter(p.getParamName())));
        }
        exp = builder.notExists(subQuery);
      } else {
        if (isIgnoreCase()) {
          exp =
              builder
                  .anyOf(getProp())
                  .toUpperCase()
                  .notLike(builder.getParameter(p.getParamName()));
        } else {
          exp = builder.anyOf(getProp()).notLike(builder.getParameter(p.getParamName()));
        }
      }
    } else {
      if (isIgnoreCase()) {
        exp = builder.get(getProp()).toUpperCase().notLike(builder.getParameter(p.getParamName()));
      } else {
        exp = builder.get(getProp()).notLike(builder.getParameter(p.getParamName()));
      }
    }

    return exp;
  }
  public void testEmployeeJoinProjectsJoinTeamLeaderJoinAddressWhereManagerIsNull() {
    ReadAllQuery query = new ReadAllQuery();
    query.setReferenceClass(Employee.class);
    query.setSelectionCriteria(query.getExpressionBuilder().get("manager").isNull());

    ReadAllQuery controlQuery = (ReadAllQuery) query.clone();
    Expression projects = query.getExpressionBuilder().anyOf("projects");
    query.addJoinedAttribute(projects);
    Expression teamLeader = projects.get("teamLeader");
    query.addJoinedAttribute(teamLeader);
    Expression teamLeaderAddress = teamLeader.get("address");
    query.addJoinedAttribute(teamLeaderAddress);

    String errorMsg = executeQueriesAndCompareResults(controlQuery, query);
    if (errorMsg.length() > 0) {
      fail(errorMsg);
    }
  }
 /**
  * INTERNAL: Rebuild myself against the base, with the values of parameters supplied by the
  * context expression. This is used for transforming a standalone expression (e.g. the join
  * criteria of a mapping) into part of some larger expression. You normally would not call this
  * directly, instead calling twist See the comment there for more details"
  */
 public Expression twistedForBaseAndContext(Expression newBase, Expression context) {
   Expression twistedBase = getBaseExpression().twistedForBaseAndContext(newBase, context);
   QueryKeyExpression result = (QueryKeyExpression) twistedBase.get(getName());
   if (shouldUseOuterJoin) {
     result.doUseOuterJoin();
   }
   if (shouldQueryToManyRelationship) {
     result.doQueryToManyRelationship();
   }
   return result;
 }
  public void setup() {
    Employee emp = (Employee) getSomeEmployees().firstElement();

    PhoneNumber phone = (PhoneNumber) emp.getPhoneNumbers().firstElement();
    String areaCode = phone.getAreaCode();
    String firstName = emp.getFirstName();

    setReferenceClass(Employee.class);

    ExpressionBuilder employeeBuilder = new ExpressionBuilder();
    Expression phones = employeeBuilder.anyOf("phoneNumbers");
    Expression whereClause =
        phones
            .get("owner")
            .get("firstName")
            .equal(firstName)
            .and(phones.get("areaCode").equal(areaCode));

    ReportQuery rq = new ReportQuery();
    rq.setSelectionCriteria(whereClause);
    rq.addAttribute("number", phones.get("number"));
    rq.setReferenceClass(Employee.class);

    setOriginalOject(getAttributeFromAll("number", (Vector) getSession().executeQuery(rq)));
    getSession().getIdentityMapAccessor().initializeAllIdentityMaps();

    String ejbqlString;
    ejbqlString =
        "SELECT phone.number FROM Employee employee, IN(employee.phoneNumbers) phone "
            + "WHERE phone.owner.firstName = \""
            + firstName
            + "\" AND phone.areaCode = \""
            + areaCode
            + "\"";

    useReportQuery();
    setEjbqlString(ejbqlString);
    super.setup();
  }
  protected Expression processPropertyBetween(
      Attribute<?, ?> attr, Expression builder, List<Parameter> parameters) {

    ConditionQueryParameter p;
    ConditionQueryParameter p1;
    // convenient expression, only used by simple case
    Expression exp = null;

    p = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
    parameters.add(p);
    p1 = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
    p1.setIndex(2);
    parameters.add(p1);

    if (attr.isCollection()) {
      if (isAllColllectionMode()) {
        ReportQuery subQuery = formReportQueryForDirectCollection(attr, builder);
        subQuery.setSelectionCriteria(
            builder
                .anyOf(getProp())
                .notBetween(
                    builder.getParameter(p.getParamName()),
                    builder.getParameter(p1.getParamName())));
        exp = builder.notExists(subQuery);
      } else if (isNoneColllectionMode()) {
        ReportQuery subQuery = formReportQueryForDirectCollection(attr, builder);
        subQuery.setSelectionCriteria(
            builder
                .anyOf(getProp())
                .between(
                    builder.getParameter(p.getParamName()),
                    builder.getParameter(p1.getParamName())));
        exp = builder.notExists(subQuery);
      } else {
        exp =
            builder
                .anyOf(getProp())
                .between(
                    builder.getParameter(p.getParamName()),
                    builder.getParameter(p1.getParamName()));
      }
    } else {
      exp =
          builder
              .get(getProp())
              .between(
                  builder.getParameter(p.getParamName()), builder.getParameter(p1.getParamName()));
    }

    return exp;
  }
  public void testEmployeeJoinManagerAddressOuterJoinManagerAddress() {
    ReadAllQuery query = new ReadAllQuery();
    query.setReferenceClass(Employee.class);
    query.setSelectionCriteria(
        query
            .getExpressionBuilder()
            .get("lastName")
            .equal("Way")
            .or(query.getExpressionBuilder().get("lastName").equal("Jones")));

    ReadAllQuery controlQuery = (ReadAllQuery) query.clone();
    Expression manager = query.getExpressionBuilder().get("manager");
    query.addJoinedAttribute(manager);
    query.addJoinedAttribute(manager.get("address"));
    Expression managersManager = manager.getAllowingNull("manager");
    query.addJoinedAttribute(managersManager);

    query.addJoinedAttribute(managersManager.get("address"));

    String errorMsg = executeQueriesAndCompareResults(controlQuery, query);
    if (errorMsg.length() > 0) {
      fail(errorMsg);
    }
  }
  /**
   * INTERNAL: This expression is built on a different base than the one we want. Rebuild it and
   * return the root of the new tree
   */
  public Expression rebuildOn(Expression newBase) {
    Expression newLocalBase = getBaseExpression().rebuildOn(newBase);
    QueryKeyExpression result = null;

    // For bug 3096634 rebuild outer joins correctly from the start.
    if (shouldUseOuterJoin) {
      result = (QueryKeyExpression) newLocalBase.getAllowingNull(getName());
    } else {
      result = (QueryKeyExpression) newLocalBase.get(getName());
    }
    if (shouldQueryToManyRelationship) {
      result.doQueryToManyRelationship();
    }
    result.setSelectIfOrderedBy(selectIfOrderedBy());
    return result;
  }
  public void testProjectOuterJoinTeamMembersJoinAddress() {
    ReadAllQuery query = new ReadAllQuery();
    query.setReferenceClass(Project.class);

    ReadAllQuery controlQuery = (ReadAllQuery) query.clone();

    Expression teamMembers = query.getExpressionBuilder().anyOfAllowingNone("teamMembers");
    query.addJoinedAttribute(teamMembers);
    Expression teamMembersAddress = teamMembers.get("address");
    query.addJoinedAttribute(teamMembersAddress);

    String errorMsg = executeQueriesAndCompareResults(controlQuery, query);
    if (errorMsg.length() > 0) {
      fail(errorMsg);
    }
  }
  public void testProblemReporterProjectJoinTeamMembersJoinAddress() {
    ReadAllQuery query = new ReadAllQuery();
    query.setReferenceClass(Project.class);
    query.setSelectionCriteria(query.getExpressionBuilder().get("name").equal("Problem Reporter"));

    ReadAllQuery controlQuery = (ReadAllQuery) query.clone();

    Expression teamMembers = query.getExpressionBuilder().anyOf("teamMembers");
    query.addJoinedAttribute(teamMembers);
    Expression teamMembersAddress = teamMembers.get("address");
    query.addJoinedAttribute(teamMembersAddress);

    String errorMsg = executeQueriesAndCompareResults(controlQuery, query);
    if (errorMsg.length() > 0) {
      fail(errorMsg);
    }
  }
  public void testProjectJoinTeamLeaderJoinAddressWhereTeamLeaderNotNull() {
    ReadAllQuery query = new ReadAllQuery();
    query.setReferenceClass(Project.class);
    Expression teamLeader = query.getExpressionBuilder().get("teamLeader");
    query.setSelectionCriteria(teamLeader.notNull());

    ReadAllQuery controlQuery = (ReadAllQuery) query.clone();

    query.addJoinedAttribute(teamLeader);
    Expression teamLeaderAddress = teamLeader.get("address");
    query.addJoinedAttribute(teamLeaderAddress);

    String errorMsg = executeQueriesAndCompareResults(controlQuery, query);
    if (errorMsg.length() > 0) {
      fail(errorMsg);
    }
  }
  private Expression buildExpressionFrom(Expression expression) {
    String name = "";
    if (getQueryable() != null) {
      name = getQueryable().getName();
    }

    if (getQueryable() != null && !getQueryable().usesAnyOf()) {
      if (!this.allowsNull) {
        return expression.get(name);
      }
      return expression.getAllowingNull(name);
    }
    if (!this.allowsNull) {
      return expression.anyOf(name);
    }
    return expression.anyOfAllowingNone(name);
  }
  protected Expression processPropertyNe(
      Attribute<?, ?> attr, Expression builder, List<Parameter> parameters) {

    ConditionQueryParameter p;
    // convenient expression, only used by simple case
    Expression exp = null;

    p = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
    parameters.add(p);
    boolean isString = JpaMetamodelHelper.isString(attr);
    if (attr.isCollection()) {
      if (isAllColllectionMode()) {
        ReportQuery subQuery = formReportQueryForDirectCollection(attr, builder);
        if (isString && isIgnoreCase()) {
          subQuery.setSelectionCriteria(
              builder.anyOf(getProp()).equalsIgnoreCase(builder.getParameter(p.getParamName())));
        } else {
          subQuery.setSelectionCriteria(
              builder.anyOf(getProp()).equal(builder.getParameter(p.getParamName())));
        }

        exp = builder.notExists(subQuery);
      } else if (isNoneColllectionMode()) {
        ReportQuery subQuery = formReportQueryForDirectCollection(attr, builder);
        if (isString && isIgnoreCase()) {
          subQuery.setSelectionCriteria(
              builder
                  .anyOf(getProp())
                  .toUpperCase()
                  .notEqual(builder.getParameter(p.getParamName())));
        } else {
          subQuery.setSelectionCriteria(
              builder.anyOf(getProp()).notEqual(builder.getParameter(p.getParamName())));
        }
        exp = builder.notExists(subQuery);
      } else {
        if (isString && isIgnoreCase()) {
          exp =
              builder
                  .anyOf(getProp())
                  .toUpperCase()
                  .notEqual(builder.getParameter(p.getParamName()));
        } else {
          exp = builder.anyOf(getProp()).notEqual(builder.getParameter(p.getParamName()));
        }
      }
    } else {
      if (JpaMetamodelHelper.isRelation(attr)) {
        if (JpaMetamodelHelper.isRelationOwner(attr)) {
          Expression exp1 = builder.get(getProp()).notEqual(builder.getParameter(p.getParamName()));
          // null is not considered as equal
          Expression exp2 = builder.get(getProp()).isNull();
          exp = exp1.or(exp2);
        } else {
          exp = builder.get(getProp()).notEqual(builder.getParameter(p.getParamName()));
        }

      } else {
        if (isString && isIgnoreCase()) {
          exp =
              builder.get(getProp()).toUpperCase().notEqual(builder.getParameter(p.getParamName()));
        } else {
          exp = builder.get(getProp()).notEqual(builder.getParameter(p.getParamName()));
        }
      }
    }

    return exp;
  }
  /**
   * @param type
   * @param builder
   * @param parameters
   * @return a fully parameterized query. A default value for this query should be set to query at a
   *     late time.
   */
  public Expression processProperty(Expression builder, List<Parameter> parameters) {
    Attribute<?, ?> attr = getAttribute();

    Operator op = getOperator();
    ConditionQueryParameter p;
    // convenient expression, only used by simple case
    Expression exp = builder.get(getProp());

    switch (op) {

        // Operator for primtive type first: lt, le, gt, ge, eq, ne, between,
        // notbetween, like(String), notlike(String), checked, unchecked
        // oneof(in(...)), notoneof(notin(...))
      case lt:
        return processPropertyLt(attr, builder, parameters);
      case le:
        return processPropertyLe(attr, builder, parameters);
      case eq:
        return processPropertyEq(attr, builder, parameters);
      case gt:
        return processPropertyGt(attr, builder, parameters);
      case ge:
        return processPropertyGe(attr, builder, parameters);
      case ne:
        return processPropertyNe(attr, builder, parameters);
      case between:
        return processPropertyBetween(attr, builder, parameters);
      case notbetween:
        return processPropertyNotBetween(attr, builder, parameters);
      case like:
        return processPropertyLike(attr, builder, parameters);
      case notlike:
        return processPropertyNotLike(attr, builder, parameters);
      case oneof:
        return processPropertyOneof(attr, builder, parameters);
      case notoneof:
        // for direct follection.
        return processPropertyNotOneof(attr, builder, parameters);
      case checked:
        exp = exp.equal(true);
        return exp;
      case unchecked:
        exp = exp.equal(false);
        return exp;
        /** *************** The above is for comparison of primitive value */
        // null, notnull are not primitive attr, only property with single
        // value
      case notnull:
        if (attr.isCollection()) {
          return this.processPropertyNotEmpty(attr, builder, parameters);
        } else {
          exp = exp.notNull();
          return exp;
        }
      case isnull:
        if (attr.isCollection()) {
          return this.processPropertyEmpty(attr, builder, parameters);
        } else {

          if (JpaMetamodelHelper.isRelation(attr) && !JpaMetamodelHelper.isRelationOwner(attr)) {
            ExpressionBuilder main = new ExpressionBuilder();
            ReportQuery subQuery = formRelationReportQuery(attr, main);
            subQuery.setSelectionCriteria(main.equal(builder.get(getProp())));
            subQuery.addAttribute("id");

            exp = builder.notExists(subQuery);

          } else {
            exp = exp.isNull();
          }
          return exp;
        }

        // -----------------size operation for collection: size, sizegt,
        // sizelt, empty, notempty
      case size:
        // do not work for simple collection.
        // need test case for simple collection
        p = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
        parameters.add(p);
        if (JpaMetamodelHelper.isRelation(attr)) {
          exp = builder.size(getProp()).equal(builder.getParameter(p.getParamName()));
        } else {
          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=396892
          exp =
              builder
                  .subQuery(this.formSizeReportQuery(attr, builder))
                  .equal(builder.getParameter(p.getParamName()));
        }
        return exp;
      case sizeGt:
        p = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
        parameters.add(p);
        if (JpaMetamodelHelper.isRelation(attr)) {
          exp = builder.size(getProp()).greaterThan(builder.getParameter(p.getParamName()));
        } else {
          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=396892
          exp =
              builder
                  .subQuery(this.formSizeReportQuery(attr, builder))
                  .greaterThan(builder.getParameter(p.getParamName()));
        }
        return exp;
      case sizeLt:
        p = new ConditionQueryParameter(this, getQuery().getPrefix() + parameters.size());
        parameters.add(p);
        if (JpaMetamodelHelper.isRelation(attr)) {
          exp = builder.size(getProp()).lessThan(builder.getParameter(p.getParamName()));
        } else {
          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=396892
          exp =
              builder
                  .subQuery(this.formSizeReportQuery(attr, builder))
                  .lessThan(builder.getParameter(p.getParamName()));
        }
        return exp;
      case empty:
        return this.processPropertyEmpty(attr, builder, parameters);
      case notempty:
        return this.processPropertyNotEmpty(attr, builder, parameters);
      case tracedown:
        if (attr.isCollection()) {
          if (isAllColllectionMode()) {
            return this.processPropertyTracedownToManyAll(attr, builder, parameters);
          } else if (isNoneColllectionMode()) {
            return this.processPropertyTracedownToManyNone(attr, builder, parameters);
          } else {
            return this.processPropertyTracedownToManySomeOptimized(attr, builder, parameters);
          }

        } else {
          return this.processPropertyTracedownTooneOptimized(attr, builder, parameters);
        }
      default:
        break;
    }
    return null;
  }
    public void test() {
      // clear cache
      getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
      // create batch read query, set its selectionCriteria
      ReadAllQuery query = new ReadAllQuery(Employee.class);
      setSelectionCriteria(query);
      // before adding batch read attributes clone the query to create control query
      ReadAllQuery controlQuery = (ReadAllQuery) query.clone();
      // add batch read attributes
      Expression managedEmployees = query.getExpressionBuilder().get("managedEmployees");
      Expression managedEmployeesPhoneNumbers = managedEmployees.get("phoneNumbers");
      query.addBatchReadAttribute(managedEmployeesPhoneNumbers);
      // execute the query
      List employees = (List) getSession().executeQuery(query);
      if (employees.isEmpty()) {
        throw new TestProblemException("No Employees were read");
      }
      // need to instantiate only a single Phone on a single managed Employee to trigger sql that
      // reads data from the db for all.
      // still need to trigger all the indirections - but (except the first one) they are not
      // accessing the db
      // (the data is already cached in the value holders).
      printDebug("Trigger batch reading results");
      boolean isConnected = true;
      for (int i = 0; i < employees.size(); i++) {
        Employee manager = (Employee) employees.get(i);
        if (!manager.getManagedEmployees().isEmpty()) {
          printDebug("Manager = " + manager);
          for (int j = 0; j < manager.getManagedEmployees().size(); j++) {
            Employee emp = (Employee) manager.getManagedEmployees().get(j);
            printDebug("     " + emp);
            for (int k = 0; k < emp.getPhoneNumbers().size(); k++) {
              if (isConnected) {
                // need to instantiate only a single Phone on a single managed Employee to trigger
                // sql that reads data from the db for all.
                // to ensure that no other sql is issued close connection.
                ((AbstractSession) getSession()).getAccessor().closeConnection();
                isConnected = false;
              }
              PhoneNumber phone = (PhoneNumber) emp.getPhoneNumbers().get(k);
              printDebug("          " + phone);
            }
          }
        } else {
          printDebug(manager.toString());
        }
      }
      if (!isConnected) {
        // reconnect connection
        ((AbstractSession) getSession())
            .getAccessor()
            .reestablishConnection((AbstractSession) getSession());
      }
      printDebug("");

      // obtain control results
      // clear cache
      getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
      // execute control query
      List controlEmployees = (List) getSession().executeQuery(controlQuery);
      // instantiate all value holders that the batch query expected to instantiate
      printDebug("Trigger control results");
      for (int i = 0; i < controlEmployees.size(); i++) {
        Employee manager = (Employee) controlEmployees.get(i);
        if (!manager.getManagedEmployees().isEmpty()) {
          printDebug("Manager = " + manager);
          for (int j = 0; j < manager.getManagedEmployees().size(); j++) {
            Employee emp = (Employee) manager.getManagedEmployees().get(j);
            printDebug("     " + emp);
            for (int k = 0; k < emp.getPhoneNumbers().size(); k++) {
              PhoneNumber phone = (PhoneNumber) emp.getPhoneNumbers().get(k);
              printDebug("          " + phone);
            }
          }
        } else {
          printDebug(manager.toString());
        }
      }

      // compare results
      String errorMsg =
          JoinedAttributeTestHelper.compareCollections(
              employees,
              controlEmployees,
              getSession().getClassDescriptor(Employee.class),
              ((AbstractSession) getSession()));
      if (errorMsg.length() > 0) {
        throw new TestErrorException(errorMsg);
      }
    }