/**
   * Method to load all unloaded fields in the FetchPlan. Recurses through the FetchPlan objects and
   * loads fields of sub-objects where needed. Used as a precursor to detaching objects at commit
   * since fields can't be loaded during the postCommit phase when the detach actually happens.
   *
   * @param state The FetchPlan state
   */
  public void loadFieldsInFetchPlan(FetchPlanState state) {
    if ((flags & FLAG_LOADINGFPFIELDS) != 0) {
      // Already in the process of loading fields in this class so skip
      return;
    }

    flags |= FLAG_LOADINGFPFIELDS;
    try {
      // Load unloaded FetchPlan fields of this object
      loadUnloadedFieldsInFetchPlan();

      // Recurse through all fields and do the same
      int[] fieldNumbers =
          ClassUtils.getFlagsSetTo(loadedFields, cmd.getAllMemberPositions(), true);
      if (fieldNumbers != null && fieldNumbers.length > 0) {
        // TODO Fix this to just access the fields of the FieldManager yet this actually does a
        // replaceField
        replaceFields(
            fieldNumbers, new LoadFieldManager(this, cmd.getSCOMutableMemberFlags(), myFP, state));
        updateLevel2CacheForFields(fieldNumbers);
      }
    } finally {
      flags &= ~FLAG_LOADINGFPFIELDS;
    }
  }
  /**
   * Method that will unload all fields that are not in the FetchPlan. This is typically for use
   * when the instance is being refreshed.
   */
  public void unloadNonFetchPlanFields() {
    int[] fpFieldNumbers = myFP.getMemberNumbers();
    int[] nonfpFieldNumbers = null;
    if (fpFieldNumbers == null || fpFieldNumbers.length == 0) {
      nonfpFieldNumbers = cmd.getAllMemberPositions();
    } else {
      int fieldCount = cmd.getMemberCount();
      if (fieldCount == fpFieldNumbers.length) {
        // No fields that arent in FetchPlan
        return;
      }

      nonfpFieldNumbers = new int[fieldCount - fpFieldNumbers.length];
      int currentFPFieldIndex = 0;
      int j = 0;
      for (int i = 0; i < fieldCount; i++) {
        if (currentFPFieldIndex >= fpFieldNumbers.length) {
          // Past end of FetchPlan fields
          nonfpFieldNumbers[j++] = i;
        } else {
          if (fpFieldNumbers[currentFPFieldIndex] == i) {
            // FetchPlan field so move to next
            currentFPFieldIndex++;
          } else {
            nonfpFieldNumbers[j++] = i;
          }
        }
      }
    }

    // Mark all non-FetchPlan fields as unloaded
    for (int i = 0; i < nonfpFieldNumbers.length; i++) {
      loadedFields[nonfpFieldNumbers[i]] = false;
    }
  }
  /**
   * Convenience accessor to return the field numbers for the input loaded and dirty field arrays.
   *
   * @param loadedFields Fields that were detached with the object
   * @param dirtyFields Fields that have been modified while detached
   * @return The field numbers of loaded or dirty fields
   */
  protected int[] getFieldNumbersOfLoadedOrDirtyFields(
      boolean[] loadedFields, boolean[] dirtyFields) {
    // Find the number of fields that are loaded or dirty
    int numFields = 0;
    for (int i = 0; i < loadedFields.length; i++) {
      if (loadedFields[i] || dirtyFields[i]) {
        numFields++;
      }
    }

    int[] fieldNumbers = new int[numFields];
    int n = 0;
    int[] allFieldNumbers = cmd.getAllMemberPositions();
    for (int i = 0; i < loadedFields.length; i++) {
      if (loadedFields[i] || dirtyFields[i]) {
        fieldNumbers[n++] = allFieldNumbers[i];
      }
    }
    return fieldNumbers;
  }
  /* (non-Javadoc)
   * @see org.datanucleus.store.fieldmanager.AbstractFieldManager#fetchObjectField(int)
   */
  @Override
  public Object fetchObjectField(int fieldNumber) {
    ClassLoaderResolver clr = ec.getClassLoaderResolver();
    AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
    RelationType relationType = mmd.getRelationType(clr);
    EmbeddedMetaData embmd = mmds.get(0).getEmbeddedMetaData();
    if (mmds.size() == 1
        && embmd != null
        && embmd.getOwnerMember() != null
        && embmd.getOwnerMember().equals(mmd.getName())) {
      // Special case of this being a link back to the owner. TODO Repeat this for nested and their
      // owners
      ObjectProvider[] ownerOps = ec.getOwnersForEmbeddedObjectProvider(op);
      return (ownerOps != null && ownerOps.length > 0 ? ownerOps[0].getObject() : null);
    }

    if (relationType != RelationType.NONE
        && MetaDataUtils.getInstance()
            .isMemberEmbedded(ec.getMetaDataManager(), clr, mmd, relationType, null)) {
      // Embedded field
      if (RelationType.isRelationSingleValued(relationType)) {
        // TODO Cater for null value detection

        List<AbstractMemberMetaData> embMmds = new ArrayList<AbstractMemberMetaData>(mmds);
        embMmds.add(mmd);

        AbstractClassMetaData embCmd =
            ec.getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
        ObjectProvider embOP =
            ec.getNucleusContext()
                .getObjectProviderFactory()
                .newForEmbedded(ec, embCmd, op, fieldNumber);
        FieldManager ffm = new FetchEmbeddedFieldManager(embOP, result, embMmds, table);
        embOP.replaceFields(embCmd.getAllMemberPositions(), ffm);
        return embOP.getObject();
      }
    }

    return fetchNonEmbeddedObjectField(mmd, relationType, clr);
  }
 private static void addColumnsToScanForEmbeddedMember(
     Scan scan, List<AbstractMemberMetaData> embMmds, Table table, ExecutionContext ec) {
   AbstractMemberMetaData lastMmd = embMmds.get(embMmds.size() - 1);
   ClassLoaderResolver clr = ec.getClassLoaderResolver();
   AbstractClassMetaData embCmd =
       ec.getMetaDataManager().getMetaDataForClass(lastMmd.getTypeName(), clr);
   int[] embMmdPosns = embCmd.getAllMemberPositions();
   for (int i = 0; i < embMmdPosns.length; i++) {
     AbstractMemberMetaData embMmd = embCmd.getMetaDataForManagedMemberAtAbsolutePosition(i);
     List<AbstractMemberMetaData> subEmbMmds = new ArrayList<AbstractMemberMetaData>(embMmds);
     subEmbMmds.add(embMmd);
     RelationType relationType = embMmd.getRelationType(clr);
     MemberColumnMapping mapping = table.getMemberColumnMappingForEmbeddedMember(subEmbMmds);
     if (RelationType.isRelationSingleValued(relationType)) {
       addColumnsToScanForEmbeddedMember(scan, subEmbMmds, table, ec);
     } else {
       String familyName = HBaseUtils.getFamilyNameForColumn(mapping.getColumn(0));
       String qualifName = HBaseUtils.getQualifierNameForColumn(mapping.getColumn(0));
       scan.addColumn(familyName.getBytes(), qualifName.getBytes());
     }
   }
 }