/*
   * INTERNAL:
   * If this query key represents a foreign reference answer the
   * base expression -> foreign reference join criteria.
   */
  public Expression mappingCriteria() {
    Expression selectionCriteria;

    // First look for a query key, then a mapping
    if (getQueryKeyOrNull() == null) {
      if ((getMapping() == null) || (!getMapping().isForeignReferenceMapping())) {
        return null;
      } else {
        // The join criteria is now twisted by the mappings.
        selectionCriteria = ((ForeignReferenceMapping) getMapping()).getJoinCriteria(this);
      }
    } else {
      if (!getQueryKeyOrNull().isForeignReferenceQueryKey()) {
        return null;
      } else {
        selectionCriteria = ((ForeignReferenceQueryKey) getQueryKeyOrNull()).getJoinCriteria();
        selectionCriteria = getBaseExpression().twist(selectionCriteria, this);
      }
    }

    if (shouldUseOuterJoin() && getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
      selectionCriteria = selectionCriteria.convertToUseOuterJoin();
    }

    return selectionCriteria;
  }
 /** INTERNAL Return true if treat was used on this expression */
 public boolean isTreatUsed() {
   if (this.hasDerivedExpressions())
     for (Expression exp : this.derivedExpressions) {
       if (exp.isTreatExpression()) {
         return true;
       }
     }
   return false;
 }
 /**
  * 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 List<Expression> copyDerivedExpressions(Map alreadyDone) {
   if (this.derivedExpressions == null) {
     return null;
   }
   List<Expression> derivedExpressionsCopy;
   synchronized (this) {
     derivedExpressionsCopy = new ArrayList(this.derivedExpressions);
   }
   List<Expression> result = new ArrayList(derivedExpressionsCopy.size());
   for (Expression exp : derivedExpressionsCopy) {
     result.add(exp.copiedVersionFrom(alreadyDone));
   }
   return result;
 }
  /**
   * 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;
  }
  /**
   * INTERNAL: Return the expression to join the main table of this node to any auxiliary tables.
   */
  public Expression additionalExpressionCriteria() {
    if (getDescriptor() == null) {
      return null;
    }

    Expression criteria = getDescriptor().getQueryManager().getAdditionalJoinExpression();
    if (getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) {
      if (isUsingOuterJoinForMultitableInheritance()) {
        Expression childrenCriteria =
            getDescriptor().getInheritancePolicy().getChildrenJoinExpression();
        childrenCriteria = getBaseExpression().twist(childrenCriteria, this);
        childrenCriteria.convertToUseOuterJoin();
        if (criteria == null) {
          criteria = childrenCriteria;
        } else {
          criteria = criteria.and(childrenCriteria);
        }
      }
    }

    return criteria;
  }
  /**
   * INTERNAL: Selection criteria is created with source foreign keys and target keys. This criteria
   * is then used to read target records from the table.
   */
  public void initializeSelectionCriteria(AbstractSession session) {
    Expression selectionCriteria = null;
    Expression expression;

    ExpressionBuilder expBuilder = new ExpressionBuilder();

    Iterator sourceKeysEnum = getSourceToTargetQueryKeyNames().keySet().iterator();

    while (sourceKeysEnum.hasNext()) {
      DatabaseField sourceKey = (DatabaseField) sourceKeysEnum.next();
      String target = (String) this.getSourceToTargetQueryKeyNames().get(sourceKey);
      expression = expBuilder.getParameter(sourceKey).equal(expBuilder.get(target));

      if (selectionCriteria == null) {
        selectionCriteria = expression;
      } else {
        selectionCriteria = expression.and(selectionCriteria);
      }
    }

    setSelectionCriteria(selectionCriteria);
  }
  /**
   * 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;
  }