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);
    }
  }
  /**
   * @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;
  }