/**
   * Compare the attributes. Return true if they are alike. Assume the passed-in attributes are
   * non-null.
   */
  private boolean compareAttributeValues(
      Object collection1, Object collection2, AbstractSession session) {
    ContainerPolicy cp = this.getContainerPolicy();

    if (cp.sizeFor(collection1) != cp.sizeFor(collection2)) {
      return false;
    }

    // if they are both empty, go no further...
    if (cp.sizeFor(collection1) == 0) {
      return true;
    }

    if (cp.hasOrder()) {
      return this.compareAttributeValuesWithOrder(collection1, collection2, session);
    } else {
      return this.compareAttributeValuesWithoutOrder(collection1, collection2, session);
    }
  }
  /**
   * INTERNAL: Get the appropriate attribute value from the object and put it in the appropriate
   * field of the database row. Loop through the reference objects and extract the primary keys and
   * put them in the vector of "nested" rows.
   */
  public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session) {
    if (!isForeignKeyRelationship) {
      return;
    }

    if (((getSourceForeignKeysToTargetKeys()) == null)
        || (getSourceForeignKeysToTargetKeys().size() == 0)) {
      return;
    }

    if (this.isReadOnly()) {
      return;
    }

    AbstractRecord referenceRow =
        this.getIndirectionPolicy().extractReferenceRow(this.getAttributeValueFromObject(object));
    if (referenceRow != null) {
      // the reference objects have not been instantiated - use the value from the original row
      if (getForeignKeyGroupingElement() != null) {
        row.put(
            this.getForeignKeyGroupingElement(),
            referenceRow.getValues(this.getForeignKeyGroupingElement()));
      } else if (getSourceForeignKeyFields().size() > 0) {
        DatabaseField foreignKeyField = (DatabaseField) getSourceForeignKeyFields().get(0);
        row.put(foreignKeyField, referenceRow.getValues(foreignKeyField));
      }
      return;
    }

    ContainerPolicy cp = this.getContainerPolicy();

    // extract the keys from the objects
    Object attributeValue = this.getRealCollectionAttributeValueFromObject(object, session);
    Vector nestedRows = new Vector(cp.sizeFor(attributeValue));

    if (getForeignKeyGroupingElement() != null) {
      for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter); ) {
        XMLRecord nestedRow =
            this.extractKeyRowFromReferenceObject(cp.next(iter, session), session, (XMLRecord) row);
        nestedRows.addElement(nestedRow);
      }
      row.add(this.getForeignKeyGroupingElement(), nestedRows);
    } else {
      DatabaseField singleField = (DatabaseField) getSourceForeignKeyFields().get(0);
      DatabaseField pkField = (DatabaseField) getSourceForeignKeysToTargetKeys().get(singleField);
      for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter); ) {
        Object singleValue =
            getReferenceDescriptor()
                .getObjectBuilder()
                .extractValueFromObjectForField(cp.next(iter, session), pkField, session);
        row.add(singleField, singleValue);
      }
    }
  }
  /** INTERNAL: */
  @Override
  public void writeFromObjectIntoRow(
      Object object, AbstractRecord row, AbstractSession session, WriteType writeType)
      throws DescriptorException {
    if (this.isReadOnly()) {
      return;
    }

    Object attributeValue = this.getAttributeValueFromObject(object);
    if (attributeValue == null) {
      row.put(this.getField(), null);
      return;
    }

    ContainerPolicy cp = this.getContainerPolicy();

    Vector nestedRows = new Vector(cp.sizeFor(attributeValue));
    Object iter = cp.iteratorFor(attributeValue);
    if (null != iter) {
      while (cp.hasNext(iter)) {
        Object element = cp.next(iter, session);
        // convert the value - if necessary
        element =
            convertObjectValueToDataValue(element, session, ((XMLRecord) row).getMarshaller());
        if (element == null) {
          XMLNullRepresentationType nullRepresentation =
              getNullPolicy().getMarshalNullRepresentation();
          if (nullRepresentation == XMLNullRepresentationType.XSI_NIL) {
            nestedRows.add(XMLRecord.NIL);
          } else if (nullRepresentation == XMLNullRepresentationType.EMPTY_NODE) {
            Node emptyNode =
                XPathEngine.getInstance()
                    .createUnownedElement(((XMLRecord) row).getDOM(), (XMLField) field);
            DOMRecord nestedRow = new DOMRecord(emptyNode);
            nestedRows.add(nestedRow);
          }
        } else {
          nestedRows.addElement(buildCompositeRow(element, session, row, writeType));
        }
      }
    }

    Object fieldValue = null;
    if (!nestedRows.isEmpty()) {
      fieldValue =
          this.getDescriptor()
              .buildFieldValueFromNestedRows(nestedRows, getStructureName(), session);
    }
    row.put(this.getField(), fieldValue);
  }
  /**
   * INTERNAL: Merge changes from the source to the target object. Simply replace the entire target
   * collection.
   */
  public void mergeIntoObject(
      Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) {
    ContainerPolicy cp = this.getContainerPolicy();
    AbstractSession session = mergeManager.getSession();

    Object sourceCollection = this.getRealCollectionAttributeValueFromObject(source, session);
    Object targetCollection = cp.containerInstance(cp.sizeFor(sourceCollection));

    for (Object iter = cp.iteratorFor(sourceCollection); cp.hasNext(iter); ) {
      Object targetElement = this.buildElementFromElement(cp.next(iter, session), mergeManager);
      cp.addInto(targetElement, targetCollection, session);
    }

    // reset the attribute to allow for set method to re-morph changes if the collection is not
    // being stored directly
    this.setRealAttributeValueInObject(target, targetCollection);
  }
  /**
   * INTERNAL: Return the value of the reference attribute or a value holder. Check whether the
   * mapping's attribute should be optimized through batch and joining.
   */
  public Object valueFromRow(
      AbstractRecord row,
      JoinedAttributeManager joinManager,
      ObjectBuildingQuery sourceQuery,
      AbstractSession executionSession)
      throws DatabaseException {
    if (((EISDescriptor) this.getDescriptor()).getDataFormat() == EISDescriptor.XML) {
      ((XMLRecord) row).setSession(executionSession);
    }

    ReadQuery targetQuery = getSelectionQuery();
    if (!isForeignKeyRelationship) {
      // if the source query is cascading then the target query must use the same settings
      if (targetQuery.isObjectLevelReadQuery()
          && (sourceQuery.shouldCascadeAllParts()
              || (sourceQuery.shouldCascadePrivateParts() && isPrivateOwned())
              || (sourceQuery.shouldCascadeByMapping() && this.cascadeRefresh))) {
        targetQuery = (ObjectLevelReadQuery) targetQuery.clone();
        ((ObjectLevelReadQuery) targetQuery)
            .setShouldRefreshIdentityMapResult(sourceQuery.shouldRefreshIdentityMapResult());
        targetQuery.setCascadePolicy(sourceQuery.getCascadePolicy());
        // CR #4365
        targetQuery.setQueryId(sourceQuery.getQueryId());
        // For queries that have turned caching off, such as aggregate collection, leave it off.
        if (targetQuery.shouldMaintainCache()) {
          targetQuery.setShouldMaintainCache(sourceQuery.shouldMaintainCache());
        }
      }

      return getIndirectionPolicy().valueFromQuery(targetQuery, row, sourceQuery.getSession());
    } else {
      if (getIndirectionPolicy().usesIndirection()) {
        EISOneToManyQueryBasedValueHolder valueholder =
            new EISOneToManyQueryBasedValueHolder(this, targetQuery, row, sourceQuery.getSession());
        return valueholder;
      } else {
        Vector subRows = getForeignKeyRows(row);

        if (subRows == null) {
          return null;
        }

        ContainerPolicy cp = this.getContainerPolicy();
        Object results = cp.containerInstance(subRows.size());

        for (int i = 0; i < subRows.size(); i++) {
          XMLRecord subRow = (XMLRecord) subRows.elementAt(i);
          subRow.setSession(executionSession);
          Object object =
              getIndirectionPolicy().valueFromQuery(targetQuery, subRow, sourceQuery.getSession());
          if (object instanceof Collection) {
            java.util.Iterator iter = ((Collection) object).iterator();
            while (iter.hasNext()) {
              cp.addInto(iter.next(), results, executionSession);
            }
          } else if (object instanceof java.util.Map) {
            java.util.Iterator iter = ((java.util.Map) object).values().iterator();
            while (iter.hasNext()) {
              cp.addInto(iter.next(), results, executionSession);
            }
          } else {
            cp.addInto(object, results, executionSession);
          }
        }
        if (cp.sizeFor(results) == 0) {
          return null;
        }
        return results;
      }
    }
  }
  /**
   * INTERNAL: All objects queried via a UnitOfWork get registered here. If the query went to the
   * database.
   *
   * <p>Involves registering the query result individually and in totality, and hence refreshing /
   * conforming is done here.
   *
   * @param result may be collection (read all) or an object (read one), or even a cursor. If in
   *     transaction the shared cache will be bypassed, meaning the result may not be originals from
   *     the parent but raw database rows.
   * @param unitOfWork the unitOfWork the result is being registered in.
   * @param arguments the original arguments/parameters passed to the query execution. Used by
   *     conforming
   * @param buildDirectlyFromRows If in transaction must construct a registered result from raw
   *     database rows.
   * @return the final (conformed, refreshed, wrapped) UnitOfWork query result
   */
  public Object registerResultInUnitOfWork(
      Object result,
      UnitOfWorkImpl unitOfWork,
      AbstractRecord arguments,
      boolean buildDirectlyFromRows) {
    // For bug 2612366: Conforming results in UOW extremely slow.
    // Replacing results with registered versions in the UOW is a part of
    // conforming and is now done while conforming to maximize performance.
    if (shouldConformResultsInUnitOfWork()
        || this.descriptor.shouldAlwaysConformResultsInUnitOfWork()) {
      return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows);
    }

    // When building directly from rows, one of the performance benefits
    // is that we no longer have to wrap and then unwrap the originals.
    // result is just a vector, not a collection of wrapped originals.
    // Also for cursors the initial connection is automatically registered.
    if (buildDirectlyFromRows) {
      List<AbstractRecord> rows = (List<AbstractRecord>) result;
      ContainerPolicy cp = getContainerPolicy();
      int size = rows.size();
      Object clones = cp.containerInstance(size);
      for (int index = 0; index < size; index++) {
        AbstractRecord row = rows.get(index);

        // null is placed in the row collection for 1-m joining to filter duplicate rows.
        if (row != null) {
          Object clone = buildObject(row);
          cp.addInto(clone, clones, unitOfWork, row, this);
        }
      }
      return clones;
    }

    ContainerPolicy cp;
    Cursor cursor = null;

    // If the query is redirected then the collection returned might no longer
    // correspond to the original container policy.  CR#2342-S.M.
    if (getRedirector() != null) {
      cp = ContainerPolicy.buildPolicyFor(result.getClass());
    } else {
      cp = getContainerPolicy();
    }

    // In the case of cursors just register the initially read collection.
    if (cp.isCursorPolicy()) {
      cursor = (Cursor) result;
      // In nested UnitOfWork session might have been session of the parent.
      cursor.setSession(unitOfWork);
      cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class);
      result = cursor.getObjectCollection();
    }

    Object clones = cp.containerInstance(cp.sizeFor(result));
    AbstractSession sessionToUse = unitOfWork.getParent();
    for (Object iter = cp.iteratorFor(result); cp.hasNext(iter); ) {
      Object object = cp.next(iter, sessionToUse);
      Object clone = registerIndividualResult(object, unitOfWork, this.joinedAttributeManager);
      cp.addInto(clone, clones, unitOfWork);
    }
    if (cursor != null) {
      cursor.setObjectCollection((Vector) clones);
      return cursor;
    } else {
      return clones;
    }
  }
  /** INTERNAL: Conform the result if specified. */
  protected Object conformResult(
      Object result,
      UnitOfWorkImpl unitOfWork,
      AbstractRecord arguments,
      boolean buildDirectlyFromRows) {
    if (getSelectionCriteria() != null) {
      ExpressionBuilder builder = getSelectionCriteria().getBuilder();
      builder.setSession(unitOfWork.getRootSession(null));
      builder.setQueryClass(getReferenceClass());
    }

    // If the query is redirected then the collection returned might no longer
    // correspond to the original container policy.  CR#2342-S.M.
    ContainerPolicy cp;
    if (getRedirector() != null) {
      cp = ContainerPolicy.buildPolicyFor(result.getClass());
    } else {
      cp = getContainerPolicy();
    }

    // This code is now a great deal different...  For one, registration is done
    // as part of conforming.  Also, this should only be called if one actually
    // is conforming.
    // First scan the UnitOfWork for conforming instances.
    // This will walk through the entire cache of registered objects.
    // Let p be objects from result not in the cache.
    // Let c be objects from cache.
    // Presently p intersect c = empty set, but later p subset c.
    // By checking cache now doesConform will be called p fewer times.
    Map indexedInterimResult =
        unitOfWork.scanForConformingInstances(
            getSelectionCriteria(), getReferenceClass(), arguments, this);

    Cursor cursor = null;
    // In the case of cursors just conform/register the initially read collection.
    if (cp.isCursorPolicy()) {
      cursor = (Cursor) result;
      cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class);
      // In nested UnitOfWork session might have been session of the parent.
      cursor.setSession(unitOfWork);
      result = cursor.getObjectCollection();
      // for later incremental conforming...
      cursor.setInitiallyConformingIndex(indexedInterimResult);
      cursor.setSelectionCriteriaClone(getSelectionCriteria());
      cursor.setTranslationRow(arguments);
    }

    // Now conform the result from the database.
    // Remove any deleted or changed objects that no longer conform.
    // Deletes will only work for simple queries, queries with or's or anyof's may not return
    // correct results when untriggered indirection is in the model.
    Vector fromDatabase = null;

    // When building directly from rows, one of the performance benefits
    // is that we no longer have to wrap and then unwrap the originals.
    // result is just a vector, not a container of wrapped originals.
    if (buildDirectlyFromRows) {
      Vector rows = (Vector) result;
      fromDatabase = new Vector(rows.size());
      for (int i = 0; i < rows.size(); i++) {
        Object object = rows.elementAt(i);
        // null is placed in the row collection for 1-m joining to filter duplicate rows.
        if (object != null) {
          Object clone =
              conformIndividualResult(
                  object,
                  unitOfWork,
                  arguments,
                  getSelectionCriteria(),
                  indexedInterimResult,
                  buildDirectlyFromRows);
          if (clone != null) {
            fromDatabase.addElement(clone);
          }
        }
      }
    } else {
      fromDatabase = new Vector(cp.sizeFor(result));
      AbstractSession sessionToUse = unitOfWork.getParent();
      for (Object iter = cp.iteratorFor(result); cp.hasNext(iter); ) {
        Object object = cp.next(iter, sessionToUse);
        Object clone =
            conformIndividualResult(
                object,
                unitOfWork,
                arguments,
                getSelectionCriteria(),
                indexedInterimResult,
                buildDirectlyFromRows);
        if (clone != null) {
          fromDatabase.addElement(clone);
        }
      }
    }

    // Now add the unwrapped conforming instances into an appropriate container.
    // Wrapping is done automatically.
    // Make sure a vector of exactly the right size is returned.
    Object conformedResult =
        cp.containerInstance(indexedInterimResult.size() + fromDatabase.size());
    Object eachClone;
    for (Iterator enumtr = indexedInterimResult.values().iterator(); enumtr.hasNext(); ) {
      eachClone = enumtr.next();
      cp.addInto(eachClone, conformedResult, unitOfWork);
    }
    for (Enumeration enumtr = fromDatabase.elements(); enumtr.hasMoreElements(); ) {
      eachClone = enumtr.nextElement();
      cp.addInto(eachClone, conformedResult, unitOfWork);
    }

    if (cursor != null) {
      cursor.setObjectCollection((Vector) conformedResult);

      // For nested UOW must copy all in object collection to
      // initiallyConformingIndex, as some of these could have been from
      // the parent UnitOfWork.
      if (unitOfWork.isNestedUnitOfWork()) {
        for (Enumeration enumtr = cursor.getObjectCollection().elements();
            enumtr.hasMoreElements(); ) {
          Object clone = enumtr.nextElement();
          indexedInterimResult.put(clone, clone);
        }
      }
      return cursor;
    } else {
      return conformedResult;
    }
  }
  public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session) {
    XMLRecord record = (XMLRecord) row;
    XMLMarshaller marshaller = record.getMarshaller();
    Object attributeValue = getAttributeValueFromObject(object);

    ContainerPolicy cp = this.getContainerPolicy();
    Vector elements = new Vector(cp.sizeFor(attributeValue));
    XMLField field = (XMLField) getField();
    NamespaceResolver resolver = field.getNamespaceResolver();
    boolean isAttribute = field.getLastXPathFragment().isAttribute();
    String prefix = null;
    XMLField includeField = null;
    if (!isAttribute) {
      if (record.isXOPPackage() && !isSwaRef() && !shouldInlineBinaryData()) {
        field = (XMLField) getField();

        // If the field's resolver is non-null and has an entry for XOP,
        // use it - otherwise, create a new resolver, set the XOP entry,
        // on it, and use it instead.
        // We do this to avoid setting the XOP namespace declaration on
        // a given field or descriptor's resolver, as it is only required
        // on the current element
        if (resolver != null) {
          prefix = resolver.resolveNamespaceURI(XMLConstants.XOP_URL);
        }
        if (prefix == null) {
          prefix = XMLConstants.XOP_PREFIX; // "xop";
          resolver = new NamespaceResolver();
          resolver.put(prefix, XMLConstants.XOP_URL);
        }
        includeField = new XMLField(prefix + XMLConstants.COLON + INCLUDE + "/@href");
        includeField.setNamespaceResolver(resolver);
      }
    }
    XMLField textField = new XMLField(field.getXPath() + '/' + XMLConstants.TEXT);
    textField.setNamespaceResolver(field.getNamespaceResolver());
    textField.setSchemaType(field.getSchemaType());
    // field = textField;

    boolean inline = false;
    for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter); ) {
      Object element = cp.next(iter, session);
      element = getValueToWrite(element, object, record, field, includeField, session);
      if (element.getClass() == ClassConstants.ABYTE) {
        inline = true;
      }
      if (element != null) {
        elements.addElement(element);
      }
    }
    Object fieldValue = null;
    if (!elements.isEmpty()) {
      fieldValue =
          this.getDescriptor()
              .buildFieldValueFromDirectValues(elements, elementDataTypeName, session);
    }
    if (inline) {
      row.put(textField, fieldValue);
    } else {
      row.put(field, fieldValue);
    }
  }