/** INTERNAL: Print SQL onto the stream, using the ExpressionPrinter for context */
  public void printSQL(ExpressionSQLPrinter printer) {
    if (isAttribute()) {
      printer.printField(getAliasedField());
    }

    // If the mapping is a direct collection then this falls into a gray area.
    // It must be treated as an attribute at this moment for it has a direct field.
    // However it is not an attribute in the sense that it also represents a foreign
    // reference and a mapping criteria has been added.
    // For bug 2900974 these are now handled as non-attributes during normalize but
    // as attributes when printing SQL.
    //
    if ((!isAttribute()) && (getMapping() != null) && getMapping().isDirectCollectionMapping()) {
      DirectCollectionMapping directCollectionMapping = (DirectCollectionMapping) getMapping();

      // The aliased table comes for free as it was a required part of the join criteria.
      TableExpression table =
          (TableExpression) getTable(directCollectionMapping.getReferenceTable());
      DatabaseTable aliasedTable = table.aliasForTable(table.getTable());
      DatabaseField aliasedField = (DatabaseField) directCollectionMapping.getDirectField().clone();
      aliasedField.setTable(aliasedTable);
      printer.printField(aliasedField);
    }

    if ((getMapping() != null) && getMapping().isNestedTableMapping()) {
      DatabaseTable tableAlias = aliasForTable(new NestedTable(this));
      printer.printString(tableAlias.getName());
    }
  }
  /**
   * INTERNAL: Returns the first field from each of the owned tables, used for fine-grained
   * pessimistic locking.
   */
  protected Vector getForUpdateOfFields() {
    Vector allFields = getFields();
    int expected = getTableAliases().size();
    Vector firstFields = new Vector(expected);
    DatabaseTable lastTable = null;
    DatabaseField field = null;
    int i = 0;

    // The following loop takes O(n*m) time.  n=# of fields. m=#tables.
    // However, in the m=1 case this will take one pass only.
    // Also assuming that fields are generally sorted by table, this will
    // take O(n) time.
    // An even faster way may be to go getDescriptor().getAdditionalPrimaryKeyFields.
    while ((i < allFields.size()) && (firstFields.size() < expected)) {
      field = (DatabaseField) allFields.elementAt(i++);
      if ((lastTable == null) || !field.getTable().equals(lastTable)) {
        lastTable = field.getTable();
        int j = 0;
        while (j < firstFields.size()) {
          if (lastTable.equals(((DatabaseField) firstFields.elementAt(j)).getTable())) {
            break;
          }
          j++;
        }
        if (j == firstFields.size()) {
          firstFields.addElement(field);
        }
      }
    }
    return firstFields;
  }
  /**
   * INTERNAL: This methods clones all the fields and ensures that each collection refers to the
   * same clones.
   */
  @Override
  public Object clone() {
    VariableOneToOneMapping clone = (VariableOneToOneMapping) super.clone();
    Map setOfKeys = new HashMap(getSourceToTargetQueryKeyNames().size());
    Map sourceToTarget = new HashMap(getSourceToTargetQueryKeyNames().size());
    Vector foreignKeys =
        org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(
            getForeignKeyFields().size());

    if (getTypeField() != null) {
      clone.setTypeField((DatabaseField) this.getTypeField().clone());
    }

    for (Iterator enumtr = getSourceToTargetQueryKeyNames().keySet().iterator();
        enumtr.hasNext(); ) {
      // Clone the SourceKeyFields
      DatabaseField field = (DatabaseField) enumtr.next();
      DatabaseField clonedField = (DatabaseField) field.clone();
      setOfKeys.put(field, clonedField);
      // on the next line I'm cloning the query key names
      sourceToTarget.put(clonedField, getSourceToTargetQueryKeyNames().get(field));
    }

    for (Enumeration enumtr = getForeignKeyFields().elements(); enumtr.hasMoreElements(); ) {
      DatabaseField field = (DatabaseField) enumtr.nextElement();
      foreignKeys.addElement(setOfKeys.get(field));
    }
    clone.setSourceToTargetQueryKeyFields(sourceToTarget);
    clone.setForeignKeyFields(foreignKeys);
    clone.setTypeIndicatorTranslation(new HashMap(this.getTypeIndicatorTranslation()));
    return clone;
  }
  /**
   * INTERNAL: Return the classification for the field contained in the mapping. This is used to
   * convert the row value to a consistent java value.
   */
  @Override
  public Class getFieldClassification(DatabaseField fieldToClassify) {
    if ((getTypeField() != null) && (fieldToClassify.equals(getTypeField()))) {
      return getTypeField().getType();
    }

    String queryKey = (String) getSourceToTargetQueryKeyNames().get(fieldToClassify);
    if (queryKey == null) {
      return null;
    }
    // Search any of the implementor descriptors for a mapping for the query-key.
    Iterator iterator =
        getReferenceDescriptor().getInterfacePolicy().getChildDescriptors().iterator();
    if (iterator.hasNext()) {
      ClassDescriptor firstChild = (ClassDescriptor) iterator.next();
      DatabaseMapping mapping = firstChild.getObjectBuilder().getMappingForAttributeName(queryKey);
      if ((mapping != null) && (mapping.isDirectToFieldMapping())) {
        return ((AbstractDirectMapping) mapping).getAttributeClassification();
      }
      QueryKey targetQueryKey = firstChild.getQueryKeyNamed(queryKey);
      if ((targetQueryKey != null) && (targetQueryKey.isDirectQueryKey())) {
        return firstChild
            .getObjectBuilder()
            .getFieldClassification(((DirectQueryKey) targetQueryKey).getField());
      }
    }
    return null;
  }
  /** INTERNAL: Alias the database field for our current environment */
  protected void initializeAliasedField() {
    DatabaseField tempField = (DatabaseField) getField().clone();
    DatabaseTable aliasedTable = getAliasedTable();

    //  Put in a special check here so that if the aliasing does nothing we don't cache the
    // result because it's invalid. This saves us from caching premature data if e.g. debugging
    // causes us to print too early"
    //	if (aliasedTable.equals(getField().getTable())) {
    //		return;
    //	} else {
    aliasedField = tempField;
    aliasedField.setTable(aliasedTable);
    //	}
  }