/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.persistence.jdbc;

import static org.apache.openjpa.persistence.jdbc.MappingTag.ASSOC_OVERRIDE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ASSOC_OVERRIDES;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ATTR_OVERRIDE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ATTR_OVERRIDES;
import static org.apache.openjpa.persistence.jdbc.MappingTag.CLASS_CRIT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.COLLECTION_TABLE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.COLUMN_RESULT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.CONTAINER_TABLE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.DATASTORE_ID_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.DISCRIM_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.DISCRIM_STRAT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.DISCRIM_VAL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.EAGER_FETCH_MODE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_CLASS_CRIT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_EMBEDDED_MAPPING;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_FK;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_INDEX;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_JOIN_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_JOIN_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_NONPOLY;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ELEM_STRAT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.EMBEDDED_MAPPING;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ENTITY_RESULT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ENUMERATED;
import static org.apache.openjpa.persistence.jdbc.MappingTag.FIELD_RESULT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.FK;
import static org.apache.openjpa.persistence.jdbc.MappingTag.INDEX;
import static org.apache.openjpa.persistence.jdbc.MappingTag.INHERITANCE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.JOIN_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.JOIN_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.JOIN_TABLE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_CLASS_CRIT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_EMBEDDED_MAPPING;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_FK;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_INDEX;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_JOIN_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_JOIN_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_NONPOLY;
import static org.apache.openjpa.persistence.jdbc.MappingTag.KEY_STRAT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAPPING_OVERRIDE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAPPING_OVERRIDES;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAP_KEY_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAP_KEY_ENUMERATED;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAP_KEY_JOIN_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAP_KEY_JOIN_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.MAP_KEY_TEMPORAL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.NONPOLY;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ORDER_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.ORDER_COLUMN;
import static org.apache.openjpa.persistence.jdbc.MappingTag.PK_JOIN_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.PK_JOIN_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.SECONDARY_TABLE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.SECONDARY_TABLES;
import static org.apache.openjpa.persistence.jdbc.MappingTag.SQL_RESULT_SET_MAPPING;
import static org.apache.openjpa.persistence.jdbc.MappingTag.SQL_RESULT_SET_MAPPINGS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.STRAT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.SUBCLASS_FETCH_MODE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.TABLE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.TABLE_GEN;
import static org.apache.openjpa.persistence.jdbc.MappingTag.TEMPORAL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.UNIQUE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.VERSION_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.VERSION_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.VERSION_STRAT;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_EMBEDDED_MAPPING;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_JOIN_COL;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_JOIN_COLS;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_MAPPING_OVERRIDE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_MAPPING_OVERRIDES;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_SECONDARY_TABLE;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_SECONDARY_TABLES;
import static org.apache.openjpa.persistence.jdbc.MappingTag.X_TABLE;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.persistence.AssociationOverride;
import jakarta.persistence.AssociationOverrides;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ColumnResult;
import jakarta.persistence.ConstructorResult;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.EntityResult;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FieldResult;
import jakarta.persistence.Inheritance;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.JoinTable;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.MapKeyEnumerated;
import jakarta.persistence.MapKeyJoinColumn;
import jakarta.persistence.MapKeyJoinColumns;
import jakarta.persistence.MapKeyTemporal;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.SecondaryTables;
import jakarta.persistence.SqlResultSetMapping;
import jakarta.persistence.SqlResultSetMappings;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;
import jakarta.persistence.Temporal;
import jakarta.persistence.UniqueConstraint;

import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
import org.apache.openjpa.jdbc.kernel.EagerFetchModes;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.ClassMappingInfo;
import org.apache.openjpa.jdbc.meta.Discriminator;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.FieldMappingInfo;
import org.apache.openjpa.jdbc.meta.MappingInfo;
import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.QueryResultMapping;
import org.apache.openjpa.jdbc.meta.QueryResultMapping.PCResult;
import org.apache.openjpa.jdbc.meta.SequenceMapping;
import org.apache.openjpa.jdbc.meta.ValueMapping;
import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
import org.apache.openjpa.jdbc.meta.strats.EnumValueHandler;
import org.apache.openjpa.jdbc.meta.strats.FlatClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Schemas;
import org.apache.openjpa.jdbc.schema.Unique;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataContext;
import org.apache.openjpa.persistence.AnnotationPersistenceMetaDataParser;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;

/**
 * Persistence annotation mapping parser.
 *
 * @author Pinaki Poddar
 * @author Steve Kim
 * @author Abe White
 */
public class AnnotationPersistenceMappingParser
    extends AnnotationPersistenceMetaDataParser {

    protected static final int TRUE = 1;
    protected static final int FALSE = 2;

    private static final Localizer _loc = Localizer.forPackage
        (AnnotationPersistenceMappingParser.class);

    private static final Map<Class<?>, MappingTag> _tags =
        new HashMap<>();

    private DBDictionary _dict;

    static {
        _tags.put(AssociationOverride.class, ASSOC_OVERRIDE);
        _tags.put(AssociationOverrides.class, ASSOC_OVERRIDES);
        _tags.put(AttributeOverride.class, ATTR_OVERRIDE);
        _tags.put(AttributeOverrides.class, ATTR_OVERRIDES);
        _tags.put(jakarta.persistence.Column.class, COL);
        _tags.put(ColumnResult.class, COLUMN_RESULT);
        _tags.put(DiscriminatorColumn.class, DISCRIM_COL);
        _tags.put(DiscriminatorValue.class, DISCRIM_VAL);
        _tags.put(ElementColumn.class, ELEM_COL);
        _tags.put(ElementColumns.class, ELEM_COLS);
        _tags.put(ElementEmbeddedMapping.class, ELEM_EMBEDDED_MAPPING);
        _tags.put(ElementStrategy.class, ELEM_STRAT);
        _tags.put(EntityResult.class, ENTITY_RESULT);
        _tags.put(Enumerated.class, ENUMERATED);
        _tags.put(FieldResult.class, FIELD_RESULT);
        _tags.put(Inheritance.class, INHERITANCE);
        _tags.put(JoinColumn.class, JOIN_COL);
        _tags.put(JoinColumns.class, JOIN_COLS);
        _tags.put(JoinTable.class, JOIN_TABLE);
        _tags.put(KeyColumn.class, KEY_COL);
        _tags.put(KeyColumns.class, KEY_COLS);
        _tags.put(KeyClassCriteria.class, KEY_CLASS_CRIT);
        _tags.put(KeyEmbeddedMapping.class, KEY_EMBEDDED_MAPPING);
        _tags.put(KeyForeignKey.class, KEY_FK);
        _tags.put(KeyIndex.class, KEY_INDEX);
        _tags.put(KeyJoinColumn.class, KEY_JOIN_COL);
        _tags.put(KeyJoinColumns.class, KEY_JOIN_COLS);
        _tags.put(KeyNonpolymorphic.class, KEY_NONPOLY);
        _tags.put(KeyStrategy.class, KEY_STRAT);
        _tags.put(MapKeyColumn.class, MAP_KEY_COL);
        _tags.put(MapKeyEnumerated.class, MAP_KEY_ENUMERATED);
        _tags.put(MapKeyJoinColumn.class, MAP_KEY_JOIN_COL);
        _tags.put(MapKeyJoinColumns.class, MAP_KEY_JOIN_COLS);
        _tags.put(MapKeyTemporal.class, MAP_KEY_TEMPORAL);
        _tags.put(PrimaryKeyJoinColumn.class, PK_JOIN_COL);
        _tags.put(PrimaryKeyJoinColumns.class, PK_JOIN_COLS);
        _tags.put(SecondaryTable.class, SECONDARY_TABLE);
        _tags.put(SecondaryTables.class, SECONDARY_TABLES);
        _tags.put(SqlResultSetMapping.class, SQL_RESULT_SET_MAPPING);
        _tags.put(SqlResultSetMappings.class, SQL_RESULT_SET_MAPPINGS);
        _tags.put(Table.class, TABLE);
        _tags.put(Temporal.class, TEMPORAL);
        _tags.put(TableGenerator.class, TABLE_GEN);
        _tags.put(ClassCriteria.class, CLASS_CRIT);
        _tags.put(Columns.class, COLS);
        _tags.put(ContainerTable.class, CONTAINER_TABLE);
        _tags.put(CollectionTable.class, COLLECTION_TABLE);
        _tags.put(DataStoreIdColumn.class, DATASTORE_ID_COL);
        _tags.put(DiscriminatorStrategy.class, DISCRIM_STRAT);
        _tags.put(EagerFetchMode.class, EAGER_FETCH_MODE);
        _tags.put(ElementClassCriteria.class, ELEM_CLASS_CRIT);
        _tags.put(ElementForeignKey.class, ELEM_FK);
        _tags.put(ElementIndex.class, ELEM_INDEX);
        _tags.put(ElementJoinColumn.class, ELEM_JOIN_COL);
        _tags.put(ElementJoinColumns.class, ELEM_JOIN_COLS);
        _tags.put(ElementNonpolymorphic.class, ELEM_NONPOLY);
        _tags.put(EmbeddedMapping.class, EMBEDDED_MAPPING);
        _tags.put(ForeignKey.class, FK);
        _tags.put(Index.class, INDEX);
        _tags.put(MappingOverride.class, MAPPING_OVERRIDE);
        _tags.put(MappingOverrides.class, MAPPING_OVERRIDES);
        _tags.put(Nonpolymorphic.class, NONPOLY);
        _tags.put(OrderColumn.class, ORDER_COL);
        _tags.put(jakarta.persistence.OrderColumn.class, ORDER_COLUMN);
        _tags.put(Strategy.class, STRAT);
        _tags.put(SubclassFetchMode.class, SUBCLASS_FETCH_MODE);
        _tags.put(Unique.class, UNIQUE);
        _tags.put(VersionColumn.class, VERSION_COL);
        _tags.put(VersionColumns.class, VERSION_COLS);
        _tags.put(VersionStrategy.class, VERSION_STRAT);
        _tags.put(XEmbeddedMapping.class, X_EMBEDDED_MAPPING);
        _tags.put(XJoinColumn.class, X_JOIN_COL);
        _tags.put(XJoinColumns.class, X_JOIN_COLS);
        _tags.put(XMappingOverride.class, X_MAPPING_OVERRIDE);
        _tags.put(XMappingOverrides.class, X_MAPPING_OVERRIDES);
        _tags.put(XSecondaryTable.class, X_SECONDARY_TABLE);
        _tags.put(XSecondaryTables.class, X_SECONDARY_TABLES);
        _tags.put(XTable.class, X_TABLE);
    }

    public AnnotationPersistenceMappingParser(JDBCConfiguration conf) {
        super(conf);
        _dict = conf.getDBDictionaryInstance();
    }

    @Override
    protected void parsePackageMappingAnnotations(Package pkg) {
        MappingTag tag;
        for (Annotation anno : pkg.getDeclaredAnnotations()) {
            tag = _tags.get(anno.annotationType());
            if (tag == null) {
                handleUnknownPackageMappingAnnotation(pkg, anno);
                continue;
            }

            switch (tag) {
                case TABLE_GEN:
                    parseTableGenerator(pkg, (TableGenerator) anno);
                    break;
                default:
                    throw new UnsupportedException(_loc.get("unsupported", pkg,
                        anno.toString()));
            }
        }
    }

    /**
     * Allow subclasses to handle unknown annotations.
     */
    protected boolean handleUnknownPackageMappingAnnotation(Package pkg,
        Annotation anno) {
        return false;
    }

    /**
     * Parse @TableGenerator.
     */
    private void parseTableGenerator(AnnotatedElement el, TableGenerator gen) {
        String name = gen.name();
        if (StringUtil.isEmpty(name))
            throw new MetaDataException(_loc.get("no-gen-name", el));

        Log log = getLog();
        if (log.isTraceEnabled())
            log.trace(_loc.get("parse-gen", name));

        SequenceMapping meta = (SequenceMapping) getRepository().
            getCachedSequenceMetaData(name);
        if (meta != null) {
            if (log.isWarnEnabled())
                log.warn(_loc.get("dup-gen", name, el));
            return;
        }

        meta = (SequenceMapping) getRepository().addSequenceMetaData(name);
        meta.setSequencePlugin(SequenceMapping.IMPL_VALUE_TABLE);
        meta.setTableIdentifier(toTableIdentifier(gen.schema(), gen.table()));
        meta.setPrimaryKeyColumnIdentifier(DBIdentifier.newColumn(gen.pkColumnName(), delimit()));
        meta.setSequenceColumnIdentifier(DBIdentifier.newColumn(gen.valueColumnName(),delimit()));
        meta.setPrimaryKeyValue(gen.pkColumnValue());
        meta.setInitialValue(gen.initialValue());
        meta.setAllocate(gen.allocationSize());
        meta.setSource(getSourceFile(), (el instanceof Class) ? el : null,
            SourceTracker.SRC_ANNOTATIONS);

        switch (gen.uniqueConstraints().length) {
        case 0:
        	break; // nothing to do
        case 1:
        	meta.setUniqueColumnsIdentifier(DBIdentifier.toArray(gen.uniqueConstraints()[0].columnNames(),
        	    DBIdentifierType.COLUMN, delimit()));
        	meta.setUniqueConstraintIdentifier(DBIdentifier.newConstraint(gen.uniqueConstraints()[0].name(),
        	    delimit()));
        	break;
        default:
        	log.warn(_loc.get("unique-many-on-seq-unsupported", el, name));
        }
    }

    @Override
    protected void parseClassMappingAnnotations(ClassMetaData meta) {
        ClassMapping cm = (ClassMapping) meta;
        Class<?> cls = cm.getDescribedType();

        MappingTag tag;
        for (Annotation anno : cls.getDeclaredAnnotations()) {
            tag = _tags.get(anno.annotationType());
            if (tag == null) {
                handleUnknownClassMappingAnnotation(cm, anno);
                continue;
            }

            switch (tag) {
                case ASSOC_OVERRIDE:
                    parseAssociationOverrides(cm, (AssociationOverride) anno);
                    break;
                case ASSOC_OVERRIDES:
                    parseAssociationOverrides(cm, ((AssociationOverrides) anno).
                        value());
                    break;
                case ATTR_OVERRIDE:
                    parseAttributeOverrides(cm, (AttributeOverride) anno);
                    break;
                case ATTR_OVERRIDES:
                    parseAttributeOverrides(cm, ((AttributeOverrides) anno).
                        value());
                    break;
                case DISCRIM_COL:
                    parseDiscriminatorColumn(cm, (DiscriminatorColumn) anno);
                    break;
                case DISCRIM_VAL:
                    cm.getDiscriminator().getMappingInfo().setValue
                        (((DiscriminatorValue) anno).value());
                    if (Modifier.isAbstract(cm.getDescribedType().
                            getModifiers()) && getLog().isInfoEnabled()) {
                        getLog().info(
                            _loc.get("discriminator-on-abstract-class", cm
                                    .getDescribedType().getName()));
                    }
                    break;
                case INHERITANCE:
                    parseInheritance(cm, (Inheritance) anno);
                    break;
                case PK_JOIN_COL:
                    parsePrimaryKeyJoinColumns(cm, (PrimaryKeyJoinColumn) anno);
                    break;
                case PK_JOIN_COLS:
                    parsePrimaryKeyJoinColumns(cm,
                        ((PrimaryKeyJoinColumns) anno).
                            value());
                    break;
                case SECONDARY_TABLE:
                    parseSecondaryTables(cm, (SecondaryTable) anno);
                    break;
                case SECONDARY_TABLES:
                    parseSecondaryTables(cm, ((SecondaryTables) anno).value());
                    break;
                case SQL_RESULT_SET_MAPPING:
                    parseSQLResultSetMappings(cm, (SqlResultSetMapping) anno);
                    break;
                case SQL_RESULT_SET_MAPPINGS:
                    parseSQLResultSetMappings(cm, ((SqlResultSetMappings) anno).
                        value());
                    break;
                case TABLE:
                    parseTable(cm, (Table) anno);
                    break;
                case TABLE_GEN:
                    parseTableGenerator(cls, (TableGenerator) anno);
                    break;
                case DATASTORE_ID_COL:
                    parseDataStoreIdColumn(cm, (DataStoreIdColumn) anno);
                    break;
                case DISCRIM_STRAT:
                    cm.getDiscriminator().getMappingInfo().setStrategy
                        (((DiscriminatorStrategy) anno).value());
                    break;
                case FK:
                    parseForeignKey(cm.getMappingInfo(), (ForeignKey) anno);
                    break;
                case MAPPING_OVERRIDE:
                    parseMappingOverrides(cm, (MappingOverride) anno);
                    break;
                case MAPPING_OVERRIDES:
                    parseMappingOverrides(cm,
                        ((MappingOverrides) anno).value());
                    break;
                case STRAT:
                    cm.getMappingInfo().setStrategy(((Strategy) anno).value());
                    break;
                case SUBCLASS_FETCH_MODE:
                    cm.setSubclassFetchMode(toEagerFetchModeConstant
                        (((SubclassFetchMode) anno).value()));
                    break;
                case VERSION_COL:
                    parseVersionColumns(cm, (VersionColumn) anno);
                    break;
                case VERSION_COLS:
                    parseVersionColumns(cm, ((VersionColumns) anno).value());
                    break;
                case VERSION_STRAT:
                    cm.getVersion().getMappingInfo().setStrategy
                        (((VersionStrategy) anno).value());
                    break;
                case X_MAPPING_OVERRIDE:
                    parseMappingOverrides(cm, (XMappingOverride) anno);
                    break;
                case X_MAPPING_OVERRIDES:
                    parseMappingOverrides(cm,
                        ((XMappingOverrides) anno).value());
                    break;
                case X_TABLE:
                case X_SECONDARY_TABLE:
                case X_SECONDARY_TABLES:
                    // no break; not supported yet
                default:
                    throw new UnsupportedException(_loc.get("unsupported", cm,
                        anno));
            }
        }
    }

    /**
     * Allow subclasses to handle unknown annotations.
     */
    protected boolean handleUnknownClassMappingAnnotation(ClassMapping cls,
        Annotation anno) {
        return false;
    }

    /**
     * Parse @AssociationOverride(s).
     */
    private void parseAssociationOverrides(ClassMapping cm,
        AssociationOverride... assocs) {
        FieldMapping sup;
        JoinColumn[] scols;
        int unique;
        List<Column> jcols;
        JoinTable joinTbl;
        for (AssociationOverride assoc : assocs) {
            if (StringUtil.isEmpty(assoc.name()))
                throw new MetaDataException(_loc.get("no-override-name", cm));
            sup = (FieldMapping) cm.getDefinedSuperclassField(assoc.name());
            if (sup == null)
                sup = (FieldMapping) cm.addDefinedSuperclassField
                    (assoc.name(), Object.class, Object.class);
            scols = assoc.joinColumns();
            joinTbl = assoc.joinTable();
            if ((scols == null || scols.length == 0) && joinTbl == null)
                //continue;
                throw new MetaDataException(_loc.get("embed-override-name",
                        sup, assoc.name()));

            if (scols != null && scols.length > 0) {
                jcols = new ArrayList<>(scols.length);
                unique = 0;
                for (JoinColumn scol : scols) {
                    unique |= (scol.unique()) ? TRUE : FALSE;
                    jcols.add(newColumn(scol));
                }
                setColumns(sup, sup.getValueInfo(), jcols, unique);
            } else if (joinTbl != null) {
                parseJoinTable(sup, joinTbl);
            }
        }
    }

    /**
     * Parse @AttributeOverride(s).
     */
    private void parseAttributeOverrides(ClassMapping cm,
        AttributeOverride... attrs) {
        FieldMapping sup;
        for (AttributeOverride attr : attrs) {
            if (StringUtil.isEmpty(attr.name()))
                throw new MetaDataException(_loc.get("no-override-name", cm));
            sup = (FieldMapping) cm.getDefinedSuperclassField(attr.name());
            if (sup == null)
                sup = (FieldMapping) cm.addDefinedSuperclassField(attr.name(),
                    Object.class, Object.class);
            if (attr.column() != null)
                parseColumns(sup, attr.column());
        }
    }

    /**
     * Parse inheritance @PrimaryKeyJoinColumn(s).
     */
    private void parsePrimaryKeyJoinColumns(ClassMapping cm,
        PrimaryKeyJoinColumn... joins) {
        List<Column> cols = new ArrayList<>(joins.length);
        for (PrimaryKeyJoinColumn join : joins)
            cols.add(newColumn(join));
        cm.getMappingInfo().setColumns(cols);
    }

    /**
     * Create a new schema column with information from the given annotation.
     */
    private Column newColumn(PrimaryKeyJoinColumn join) {
        Column col = new Column();
        col.setFlag(Column.FLAG_PK_JOIN, true);
        if (!StringUtil.isEmpty(join.name()))
            col.setIdentifier(DBIdentifier.newColumn(join.name(), delimit()));
        if (!StringUtil.isEmpty(join.columnDefinition()))
            col.setTypeIdentifier(DBIdentifier.newColumnDefinition(join.columnDefinition()));
        if (!StringUtil.isEmpty(join.referencedColumnName()))
            setTargetIdentifier(col, join.referencedColumnName());
        return col;
    }

    /**
     * Parse @SecondaryTable(s).
     */
    private void parseSecondaryTables(ClassMapping cm,
        SecondaryTable... tables) {
        ClassMappingInfo info = cm.getMappingInfo();

        List<Column> joins = null;
        for (SecondaryTable table : tables) {
            DBIdentifier sName = DBIdentifier.newTable(table.name(), delimit());
            if (DBIdentifier.isEmpty(sName))
                throw new MetaDataException(_loc.get("second-name", cm));
            if (!StringUtil.isEmpty(table.schema())) {
                DBIdentifier sSchema = DBIdentifier.newSchema(table.schema(), delimit());
                sName = QualifiedDBIdentifier.newPath(sSchema, sName);
            }
            if (table.pkJoinColumns().length > 0) {
                joins = new ArrayList<>(table.pkJoinColumns().length);
                for (PrimaryKeyJoinColumn join : table.pkJoinColumns())
                    joins.add(newColumn(join));
                info.setSecondaryTableJoinColumns(sName, joins);
            } else {
            	info.addSecondaryTable(sName);
            }
            addUniqueConstraints(sName.getName(), cm, info, table.uniqueConstraints());
        }
    }

    /**
     * Set class table.
     */
    private void parseTable(ClassMapping cm, Table table) {
        if (cm.isAbstract())
            throw new UserException(_loc.get("table-not-allowed", cm));
        DBIdentifier tName = toTableIdentifier(table.schema(), table.name());
        if (!DBIdentifier.isNull(tName)) {
            cm.getMappingInfo().setTableIdentifier(tName);
        }
        addUniqueConstraints(tName.getName(), cm, cm.getMappingInfo(),
            table.uniqueConstraints());
        addIndices(tName.getName(), cm, cm.getMappingInfo(), table.indexes());
    }

    Unique createUniqueConstraint(MetaDataContext ctx, UniqueConstraint anno) {
        String[] columnNames = anno.columnNames();
        if (columnNames == null || columnNames.length == 0)
            throw new UserException(_loc.get("unique-no-column", ctx));
        DBIdentifier[] sColNames = DBIdentifier.toArray(columnNames,DBIdentifierType.COLUMN, delimit());
        Unique uniqueConstraint = new Unique();
        for (DBIdentifier sColName : sColNames) {
            if (DBIdentifier.isEmpty(sColName))
                throw new UserException(_loc.get("unique-empty-column",
                        Arrays.toString(sColNames), ctx));
            Column column = new Column();
            column.setIdentifier(sColName);
            uniqueConstraint.addColumn(column);
        }
        if (!StringUtil.isEmpty(anno.name())) {
            uniqueConstraint.setIdentifier(DBIdentifier.newConstraint(anno.name(), delimit()));
        }
        return uniqueConstraint;
    }

    void addUniqueConstraints(String table, MetaDataContext ctx,
        MappingInfo info, UniqueConstraint... uniqueConstraints) {
        for (UniqueConstraint anno : uniqueConstraints) {
            Unique unique = createUniqueConstraint(ctx, anno);
            unique.setTableIdentifier(DBIdentifier.newTable(table, delimit()));
            if (info instanceof ClassMappingInfo)
                ((ClassMappingInfo) info).addUnique(DBIdentifier.newTable(table), unique);
            else if (info instanceof FieldMappingInfo)
                ((FieldMappingInfo) info).addJoinTableUnique(unique);
            else
                throw new InternalException();
        }
    }


    org.apache.openjpa.jdbc.schema.Index createIndex(MetaDataContext ctx, jakarta.persistence.Index anno) {
        String columnNames = anno.columnList();
        if (StringUtil.isEmpty(columnNames)) {
            throw new UserException(_loc.get("index-no-column", ctx));
        }

        DBIdentifier[] sColNames = DBIdentifier.toArray(columnNames.split(","), DBIdentifierType.COLUMN, delimit());
        org.apache.openjpa.jdbc.schema.Index indx = new org.apache.openjpa.jdbc.schema.Index();
        for (DBIdentifier sColName : sColNames) {
            if (DBIdentifier.isEmpty(sColName))
                throw new UserException(_loc.get("index-empty-column",
                        Arrays.toString(sColNames), ctx));
            Column column = new Column();
            column.setIdentifier(sColName);
            indx.addColumn(column);
        }
        indx.setUnique(anno.unique());
        if (!StringUtil.isEmpty(anno.name())) {
            indx.setIdentifier(DBIdentifier.newConstraint(anno.name(), delimit()));
        }
        return indx;
    }

    void addIndices(String table, MetaDataContext ctx,
        MappingInfo info, jakarta.persistence.Index... indices) {
        for (jakarta.persistence.Index anno : indices) {
            org.apache.openjpa.jdbc.schema.Index idx = createIndex(ctx, anno);
            idx.setTableIdentifier(DBIdentifier.newTable(table, delimit()));
            if (info instanceof ClassMappingInfo)
                ((ClassMappingInfo) info).addIndex(DBIdentifier.newTable(table), idx);
            else
                throw new InternalException();
        }
    }

    /**
     * Form a qualified table name from a schema and table name.
     */
    private DBIdentifier toTableIdentifier(String schema, String table) {
        if (StringUtil.isEmpty(table)) {
            return DBIdentifier.NULL;
        }
        DBIdentifier tName = DBIdentifier.newTable(table, delimit());
        DBIdentifier sName = DBIdentifier.newSchema(schema, delimit());
        if (DBIdentifier.isEmpty(tName) || DBIdentifier.isEmpty(sName)) {
            return tName;
        }
        return QualifiedDBIdentifier.newPath(sName, tName);
    }

    /**
     * Parses the given annotation to create and cache a
