/**
   * INTERNAL: For CR#2456 if this is part of an objExp.equal(objExp), do not need to add additional
   * expressions to normalizer both times, and the foreign key join replaces the equal expression.
   */
  public Expression normalize(ExpressionNormalizer normalizer, Vector foreignKeyJoinPointer) {
    if (hasBeenNormalized()) {
      return this;
    }
    super.normalize(normalizer);

    setHasBeenNormalized(true);
    if ((getMapping() != null) && getMapping().isDirectToXMLTypeMapping()) {
      normalizer.getStatement().setRequiresAliases(true);
    }

    // Check if any joins need to be added.
    if (isAttribute()) {
      return this;
    }

    // If the mapping is 'ref' or 'structure', no join needed.
    if ((getMapping() != null)
        && (getMapping().isReferenceMapping() || getMapping().isStructureMapping())) {
      normalizer.getStatement().setRequiresAliases(true);
      return this;
    }

    // Compute if a distinct is required during normalization.
    if (shouldQueryToManyRelationship()
        && (!normalizer.getStatement().isDistinctComputed())
        && (!normalizer.getStatement().isAggregateSelect())) {
      normalizer.getStatement().useDistinct();
    }

    // Turn off DISTINCT if nestedTableMapping is used (not supported by Oracle 8.1.5).
    if ((getMapping() != null) && getMapping().isNestedTableMapping()) {
      // There are two types of nested tables, one used by clients, one used by mappings, do nothing
      // in the mapping case.
      if (!shouldQueryToManyRelationship()) {
        return this;
      }
      normalizer.getStatement().dontUseDistinct();
    }

    Expression mappingExpression = mappingCriteria();
    if (mappingExpression != null) {
      mappingExpression = mappingExpression.normalize(normalizer);
    }
    if (mappingExpression != null) {
      // If the join was an outer join we must not add the join criteria to the where clause,
      // if the platform prints the join in the from clause.
      if (shouldUseOuterJoin() && (getSession().getPlatform().isInformixOuterJoin())) {
        normalizer.getStatement().getOuterJoinExpressions().addElement(this);
        normalizer.getStatement().getOuterJoinedMappingCriteria().addElement(mappingExpression);
        normalizer.addAdditionalExpression(mappingExpression.and(additionalExpressionCriteria()));
        return this;
      } else if ((shouldUseOuterJoin() || isUsingOuterJoinForMultitableInheritance())
          && (!getSession().getPlatform().shouldPrintOuterJoinInWhereClause())) {
        if (shouldUseOuterJoin()) {
          normalizer.getStatement().getOuterJoinExpressions().addElement(this);
          normalizer.getStatement().getOuterJoinedMappingCriteria().addElement(mappingExpression);
          normalizer
              .getStatement()
              .getOuterJoinedAdditionalJoinCriteria()
              .addElement(additionalExpressionCriteriaMap());
          normalizer.getStatement().getDescriptorsForMultitableInheritanceOnly().add(null);
          return this;
        } else {
          if (isUsingOuterJoinForMultitableInheritance()) {
            normalizer.getStatement().getOuterJoinExpressions().addElement(null);
            normalizer.getStatement().getOuterJoinedMappingCriteria().addElement(null);
            normalizer
                .getStatement()
                .getOuterJoinedAdditionalJoinCriteria()
                .addElement(additionalExpressionCriteriaMap());
            normalizer
                .getStatement()
                .getDescriptorsForMultitableInheritanceOnly()
                .add(getMapping().getReferenceDescriptor());
            // fall through to the main case
          }
        }
      }

      // This must be added even if outer. Actually it should be converted to use a right outer
      // join, but that gets complex
      // so we do not support this current which is a limitation in some cases.
      if (foreignKeyJoinPointer != null) {
        // If this expression is right side of an objExp.equal(objExp), one
        // need not add additionalExpressionCriteria twice.
        // Also the join will replace the original objExp.equal(objExp).
        // For CR#2456.
        foreignKeyJoinPointer.add(mappingExpression);
      } else {
        normalizer.addAdditionalExpression(mappingExpression.and(additionalExpressionCriteria()));
      }
    }

    // For bug 2900974 special code for DirectCollectionMappings moved to printSQL.
    return this;
  }
  public Expression normalize(
      ExpressionNormalizer normalizer, Expression base, List<Expression> foreignKeyJoinPointer) {
    // need to determine what type this is, as it may need to change the expression its based off
    // slightly
    if (this.hasBeenNormalized) {
      return this;
    }
    this.hasBeenNormalized = true;

    Expression typeExpression = getTypeClause();
    typeExpression.normalize(normalizer);

    if (this.baseExpression != null) { // should never be null
      // First normalize the base.
      setBaseExpression(this.baseExpression.normalize(normalizer));
      if (getAsOfClause() == null) {
        asOf(this.baseExpression.getAsOfClause());
      }
    }

    // This class has no validation but we should still make the method call for consistency
    // bug # 2956674
    // validation is moved into normalize to ensure that expressions are valid before we attempt to
    // work with them
    validateNode();
    // the following is based on QueryKey.normalize

    SQLSelectStatement statement = normalizer.getStatement();
    // no longer directly normalize the typeExpressionBase, or find a way to use it
    this.typeExpressionBase = (ObjectExpression) this.typeExpressionBase.normalize(normalizer);

    // Normalize the ON clause if present.  Need to use rebuild, not twist as parameters are real
    // parameters.
    if (this.onClause != null) { // not sure this is needed/valid
      this.onClause = this.onClause.normalize(normalizer);
    }

    ClassDescriptor parentDescriptor = this.typeExpressionBase.getDescriptor();
    boolean isSTI = getOwnedSubTables().isEmpty();
    // only really valid if it has inheritance, but better this code than skipping it into the joins

    if (isSTI) {
      if (foreignKeyJoinPointer != null) {
        // If this expression is right side of an objExp.equal(objExp), one
        // need not add additionalExpressionCriteria twice.
        // Also the join will replace the original objExp.equal(objExp).
        // For CR#2456.
        foreignKeyJoinPointer.add(typeExpression.and(this.onClause));
      } else {
        // this just and's in the entire expression to the normalizer's expression.
        // Need to use this for TYPE and none-outerjoin components
        normalizer.addAdditionalLocalExpression(typeExpression.and(this.onClause));
      }
      return this;
    }

    // if shouldPrintOuterJoinInWhereClause is true, this is this child's tables joined together in
    // one expression
    Expression treatJoinTableExpressions = getTreatCriteria();

    boolean parentUsingOuterJoinForMultitableInheritance =
        typeExpressionBase.isUsingOuterJoinForMultitableInheritance();

    if (treatJoinTableExpressions != null) {
      treatJoinTableExpressions = treatJoinTableExpressions.normalize(normalizer);
    }
    Integer postition = typeExpressionBase.getOuterJoinExpIndex();
    if (postition != null) {
      if (parentUsingOuterJoinForMultitableInheritance) {
        // outer join was done, so our class' tables would have been included
        return this;
      }

      if (getSession().getPlatform().isInformixOuterJoin()) {
        normalizer.addAdditionalLocalExpression(
            typeExpression.and(additionalTreatExpressionCriteria()).and(this.onClause));
        return this;
      } else if (((!getSession().getPlatform().shouldPrintOuterJoinInWhereClause()))
          || (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) {

        // Adds the left joins from treat to the base QKE joins.
        Map<DatabaseTable, Expression> map =
            statement
                .getOuterJoinExpressionsHolders()
                .get(postition)
                .outerJoinedAdditionalJoinCriteria;
        if (map != null) {
          map.putAll(additionalTreatExpressionCriteriaMap());
        } else {
          statement
                  .getOuterJoinExpressionsHolders()
                  .get(postition)
                  .outerJoinedAdditionalJoinCriteria =
              additionalTreatExpressionCriteriaMap();
        }
        return this;
      }
    } else if (!getSession().getPlatform().shouldPrintOuterJoinInWhereClause()
        || (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) {
      // the base is not using an outer join, so we add a new one for this class' tables.
      Map additionalExpMap = additionalTreatExpressionCriteriaMap();
      if (additionalExpMap != null && !additionalExpMap.isEmpty()) {
        statement.addOuterJoinExpressionsHolders(additionalExpMap, parentDescriptor);
      }
    }
    typeExpression = typeExpression.normalize(normalizer);

    if (foreignKeyJoinPointer != null) {
      // If this expression is right side of an objExp.equal(objExp), one
      // need not add additionalExpressionCriteria twice.
      // Also the join will replace the original objExp.equal(objExp).
      // For CR#2456.
      foreignKeyJoinPointer.add(typeExpression.and(this.onClause));
    } else {
      // this just and's in the entire expression to the normalizer's expression.  Need to use this
      // for TYPE and non-outerjoin components
      normalizer.addAdditionalLocalExpression(
          typeExpression.and(additionalTreatExpressionCriteria()).and(this.onClause));
    }
    return this;
  }