/**
   * Accessor for the primary key for this table. Overrides the method in TableImpl to add on any
   * specification of PK name in the <join> metadata.
   *
   * @return The primary key.
   */
  public PrimaryKey getPrimaryKey() {
    PrimaryKey pk = super.getPrimaryKey();
    if (mmd.getJoinMetaData() != null) {
      PrimaryKeyMetaData pkmd = mmd.getJoinMetaData().getPrimaryKeyMetaData();
      if (pkmd != null && pkmd.getName() != null) {
        pk.setName(pkmd.getName());
      }
    }

    return pk;
  }
  /**
   * Convenience method to apply the user specification of <primary-key> columns
   *
   * @param pkmd MetaData for the primary key
   */
  protected void applyUserPrimaryKeySpecification(PrimaryKeyMetaData pkmd) {
    ColumnMetaData[] pkCols = pkmd.getColumnMetaData();
    for (int i = 0; i < pkCols.length; i++) {
      String colName = pkCols[i].getName();
      boolean found = false;
      for (int j = 0; j < ownerMapping.getNumberOfDatastoreMappings(); j++) {
        if (ownerMapping
            .getDatastoreMapping(j)
            .getColumn()
            .getIdentifier()
            .getName()
            .equals(colName)) {
          ownerMapping.getDatastoreMapping(j).getColumn().setPrimaryKey();
          found = true;
        }
      }

      if (!found) {
        for (int j = 0; j < keyMapping.getNumberOfDatastoreMappings(); j++) {
          if (keyMapping
              .getDatastoreMapping(j)
              .getColumn()
              .getIdentifier()
              .getName()
              .equals(colName)) {
            keyMapping.getDatastoreMapping(j).getColumn().setPrimaryKey();
            found = true;
          }
        }
      }

      if (!found) {
        for (int j = 0; j < valueMapping.getNumberOfDatastoreMappings(); j++) {
          if (valueMapping
              .getDatastoreMapping(j)
              .getColumn()
              .getIdentifier()
              .getName()
              .equals(colName)) {
            valueMapping.getDatastoreMapping(j).getColumn().setPrimaryKey();
            found = true;
          }
        }
      }

      if (!found) {
        throw new NucleusUserException(Localiser.msg("057040", toString(), colName));
      }
    }
  }
  /**
   * Method to initialise the table definition.
   *
   * @param clr The ClassLoaderResolver
   */
  public void initialize(ClassLoaderResolver clr) {
    assertIsUninitialized();

    MapMetaData mapmd = mmd.getMap();
    if (mapmd == null) {
      throw new NucleusUserException(Localiser.msg("057017", mmd));
    }

    PrimaryKeyMetaData pkmd =
        (mmd.getJoinMetaData() != null ? mmd.getJoinMetaData().getPrimaryKeyMetaData() : null);
    boolean pkColsSpecified = (pkmd != null && pkmd.getColumnMetaData() != null);
    boolean pkRequired = requiresPrimaryKey();

    // Add owner mapping
    ColumnMetaData[] ownerColmd = null;
    if (mmd.getJoinMetaData() != null
        && mmd.getJoinMetaData().getColumnMetaData() != null
        && mmd.getJoinMetaData().getColumnMetaData().length > 0) {
      // Column mappings defined at this side (1-N, M-N)
      // When specified at this side they use the <join> tag
      ownerColmd = mmd.getJoinMetaData().getColumnMetaData();
    }
    ownerMapping =
        ColumnCreator.createColumnsForJoinTables(
            clr.classForName(ownerType),
            mmd,
            ownerColmd,
            storeMgr,
            this,
            pkRequired,
            false,
            FieldRole.ROLE_OWNER,
            clr);
    if (NucleusLogger.DATASTORE.isDebugEnabled()) {
      logMapping(mmd.getFullFieldName() + ".[OWNER]", ownerMapping);
    }

    String keyValueFieldName =
        (mmd.getKeyMetaData() != null ? mmd.getKeyMetaData().getMappedBy() : null);
    String valueKeyFieldName =
        (mmd.getValueMetaData() != null ? mmd.getValueMetaData().getMappedBy() : null);

    // Add key mapping
    boolean keyPC = (mmd.hasMap() && mmd.getMap().keyIsPersistent());
    Class keyCls = clr.classForName(mapmd.getKeyType());
    if (keyValueFieldName != null && isEmbeddedValuePC()) {
      // Added in value code
    } else if (isSerialisedKey()
        || isEmbeddedKeyPC()
        || (isEmbeddedKey() && !keyPC)
        || ClassUtils.isReferenceType(keyCls)) {
      // Key = PC(embedded), PC(serialised), Non-PC(serialised), Non-PC(embedded), Reference
      keyMapping = storeMgr.getMappingManager().getMapping(this, mmd, clr, FieldRole.ROLE_MAP_KEY);
      if (Boolean.TRUE.equals(mmd.getContainer().allowNulls())) {
        // Make all key col(s) nullable so we can store null elements
        for (int i = 0; i < keyMapping.getNumberOfDatastoreMappings(); i++) {
          Column elementCol = keyMapping.getDatastoreMapping(i).getColumn();
          elementCol.setNullable(true);
        }
      }
      if (NucleusLogger.DATASTORE.isDebugEnabled()) {
        logMapping(mmd.getFullFieldName() + ".[KEY]", keyMapping);
      }
      if (valueKeyFieldName != null && isEmbeddedKeyPC()) {
        // Key (PC) is embedded and value is a field of the key
        EmbeddedKeyPCMapping embMapping = (EmbeddedKeyPCMapping) keyMapping;
        valueMapping = embMapping.getJavaTypeMapping(valueKeyFieldName);
      }
    } else {
      // Key = PC
      ColumnMetaData[] keyColmd = null;
      KeyMetaData keymd = mmd.getKeyMetaData();
      if (keymd != null
          && keymd.getColumnMetaData() != null
          && keymd.getColumnMetaData().length > 0) {
        // Column mappings defined at this side (1-N, M-N)
        keyColmd = keymd.getColumnMetaData();
      }
      keyMapping =
          ColumnCreator.createColumnsForJoinTables(
              keyCls, mmd, keyColmd, storeMgr, this, false, false, FieldRole.ROLE_MAP_KEY, clr);
      if (mmd.getContainer().allowNulls() == Boolean.TRUE) {
        // Make all key col(s) nullable so we can store null elements
        for (int i = 0; i < keyMapping.getNumberOfDatastoreMappings(); i++) {
          Column elementCol = keyMapping.getDatastoreMapping(i).getColumn();
          elementCol.setNullable(true);
        }
      }
      if (NucleusLogger.DATASTORE.isDebugEnabled()) {
        logMapping(mmd.getFullFieldName() + ".[KEY]", keyMapping);
      }
    }

    // Add value mapping
    boolean valuePC = (mmd.hasMap() && mmd.getMap().valueIsPersistent());
    Class valueCls = clr.classForName(mapmd.getValueType());
    if (valueKeyFieldName != null && isEmbeddedKeyPC()) {
      // Added in key code
    } else if (isSerialisedValue()
        || isEmbeddedValuePC()
        || (isEmbeddedValue() && !valuePC)
        || ClassUtils.isReferenceType(valueCls)) {
      // Value = PC(embedded), PC(serialised), Non-PC(serialised), Non-PC(embedded), Reference
      valueMapping =
          storeMgr.getMappingManager().getMapping(this, mmd, clr, FieldRole.ROLE_MAP_VALUE);
      if (mmd.getContainer().allowNulls() == Boolean.TRUE) {
        // Make all value col(s) nullable so we can store null elements
        for (int i = 0; i < valueMapping.getNumberOfDatastoreMappings(); i++) {
          Column elementCol = valueMapping.getDatastoreMapping(i).getColumn();
          elementCol.setNullable(true);
        }
      }
      if (NucleusLogger.DATASTORE.isDebugEnabled()) {
        logMapping(mmd.getFullFieldName() + ".[VALUE]", valueMapping);
      }
      if (keyValueFieldName != null && isEmbeddedValuePC()) {
        // Value (PC) is embedded and key is a field of the value
        EmbeddedValuePCMapping embMapping = (EmbeddedValuePCMapping) valueMapping;
        keyMapping = embMapping.getJavaTypeMapping(keyValueFieldName);
      }
    } else {
      // Value = PC
      ColumnMetaData[] valueColmd = null;
      ValueMetaData valuemd = mmd.getValueMetaData();
      if (valuemd != null
          && valuemd.getColumnMetaData() != null
          && valuemd.getColumnMetaData().length > 0) {
        // Column mappings defined at this side (1-N, M-N)
        valueColmd = valuemd.getColumnMetaData();
      }
      valueMapping =
          ColumnCreator.createColumnsForJoinTables(
              clr.classForName(mapmd.getValueType()),
              mmd,
              valueColmd,
              storeMgr,
              this,
              false,
              true,
              FieldRole.ROLE_MAP_VALUE,
              clr);
      if (mmd.getContainer().allowNulls() == Boolean.TRUE) {
        // Make all value col(s) nullable so we can store null elements
        for (int i = 0; i < valueMapping.getNumberOfDatastoreMappings(); i++) {
          Column elementCol = valueMapping.getDatastoreMapping(i).getColumn();
          elementCol.setNullable(true);
        }
      }
      if (NucleusLogger.DATASTORE.isDebugEnabled()) {
        logMapping(mmd.getFullFieldName() + ".[VALUE]", valueMapping);
      }
    }

    // Add order mapping if required
    boolean orderRequired = false;
    if (mmd.getOrderMetaData() != null) {
      // User requested order column so add one
      orderRequired = true;
    } else if (requiresPrimaryKey() && !pkColsSpecified) {
      // PK is required so maybe need to add an index to form the PK
      if (isEmbeddedKeyPC()) {
        if (mmd.getMap().getKeyClassMetaData(clr, storeMgr.getMetaDataManager()).getIdentityType()
            != IdentityType.APPLICATION) {
          // Embedded key PC with datastore id so we need an index to form the PK
          orderRequired = true;
        }
      } else if (isSerialisedKey()) {
        // Serialised key, so need an index to form the PK
        orderRequired = true;
      } else if (keyMapping instanceof ReferenceMapping) {
        // ReferenceMapping, so have order if more than 1 implementation
        ReferenceMapping refMapping = (ReferenceMapping) keyMapping;
        if (refMapping.getJavaTypeMapping().length > 1) {
          orderRequired = true;
        }
      } else if (!(keyMapping instanceof PersistableMapping)) {
        // Non-PC, so depends if the key column can be used as part of a PK
        // TODO This assumes the keyMapping has a single column but what if it is Color with 4 cols?
        Column elementCol = keyMapping.getDatastoreMapping(0).getColumn();
        if (!storeMgr.getDatastoreAdapter().isValidPrimaryKeyType(elementCol.getJdbcType())) {
          // Not possible to use this Non-PC type as part of the PK
          orderRequired = true;
        }
      }
    }
    if (orderRequired) {
      // Order/Adapter (index) column is required (integer based)
      ColumnMetaData orderColmd = null;
      if (mmd.getOrderMetaData() != null
          && mmd.getOrderMetaData().getColumnMetaData() != null
          && mmd.getOrderMetaData().getColumnMetaData().length > 0) {
        // Specified "order" column info
        orderColmd = mmd.getOrderMetaData().getColumnMetaData()[0];
        if (orderColmd.getName() == null) {
          // No column name so use default
          orderColmd = new ColumnMetaData(orderColmd);
          DatastoreIdentifier id = storeMgr.getIdentifierFactory().newIndexFieldIdentifier(mmd);
          orderColmd.setName(id.getName());
        }
      } else {
        // No column name so use default
        DatastoreIdentifier id = storeMgr.getIdentifierFactory().newIndexFieldIdentifier(mmd);
        orderColmd = new ColumnMetaData();
        orderColmd.setName(id.getName());
      }
      orderMapping =
          storeMgr
              .getMappingManager()
              .getMapping(int.class); // JDO2 spec [18.5] order column is assumed to be "int"
      ColumnCreator.createIndexColumn(
          orderMapping, storeMgr, clr, this, orderColmd, pkRequired && !pkColsSpecified);
      if (NucleusLogger.DATASTORE.isDebugEnabled()) {
        logMapping(mmd.getFullFieldName() + ".[ORDER]", orderMapping);
      }
    }

    // Define primary key of the join table (if any)
    if (pkRequired) {
      if (pkColsSpecified) {
        // Apply the users PK specification
        applyUserPrimaryKeySpecification(pkmd);
      } else {
        // Define PK using internal rules
        if (orderRequired) {
          // Order column specified so owner+order are the PK
          orderMapping.getDatastoreMapping(0).getColumn().setPrimaryKey();
        } else {
          // No order column specified so owner+key are the PK
          for (int i = 0; i < keyMapping.getNumberOfDatastoreMappings(); i++) {
            keyMapping.getDatastoreMapping(i).getColumn().setPrimaryKey();
          }
        }
      }
    }

    if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) {
      NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("057023", this));
    }
    storeMgr.registerTableInitialized(this);
    state = TABLE_STATE_INITIALIZED;
  }