/*
 * 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.meta;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.datacache.CacheDistributionPolicy;
import org.apache.openjpa.datacache.DataCache;
import org.apache.openjpa.enhance.PCRegistry;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.Reflection;
import org.apache.openjpa.lib.conf.Value;
import org.apache.openjpa.lib.conf.ValueListener;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.util.ClassUtil;
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.lib.xml.Commentable;
import org.apache.openjpa.util.BigDecimalId;
import org.apache.openjpa.util.BigIntegerId;
import org.apache.openjpa.util.BooleanId;
import org.apache.openjpa.util.ByteId;
import org.apache.openjpa.util.CharId;
import org.apache.openjpa.util.DateId;
import org.apache.openjpa.util.DoubleId;
import org.apache.openjpa.util.FloatId;
import org.apache.openjpa.util.GeneralException;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.IntId;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.LongId;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.ObjectId;
import org.apache.openjpa.util.OpenJPAId;
import org.apache.openjpa.util.ShortId;
import org.apache.openjpa.util.StringId;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UuidId;


/**
 * Contains metadata about a persistent type.
 * This metadata is available both at enhancement time and runtime.
 *  Note that this class employs aggressive caching, and therefore it is
 * important to finalize the configuration of field metadatas before invoking
 * methods that depend on that configuration, such as
 * {@link #getPrimaryKeyFields}.
 *
 * @author Abe White
 */
public class ClassMetaData
    extends Extensions
    implements Comparable<ClassMetaData>, SourceTracker, MetaDataContext,
    MetaDataModes, Commentable, ValueListener {

    private static final long serialVersionUID = 1L;

    /**
     * Unknown identity type.
     */
    public static final int ID_UNKNOWN = 0;

    /**
     * Datastore identity type.
     */
    public static final int ID_DATASTORE = 1;

    /**
     * Application identity type.
     */
    public static final int ID_APPLICATION = 2;

    /**
     * Unknown access type.
     */
    public static final int ACCESS_UNKNOWN = AccessCode.UNKNOWN;

    /**
     * Persistent attributes are accessed via direct field access. Bit flag.
     */
    public static final int ACCESS_FIELD = AccessCode.FIELD;

    /**
     * Persistent attributes are accessed via setters and getters. Bit flag.
     */
    public static final int ACCESS_PROPERTY = AccessCode.PROPERTY;

    /**
     * Persistent class has explicitly defined an access type.
     * This will allow the attributes to use mixed access i.e. some field
     * may use ACCESS_FIELD while others ACCESS_PROPERTY.
     */
    public static final int ACCESS_EXPLICIT = AccessCode.EXPLICIT;

    /**
     * Value for using a synthetic detached state field, which is the default.
     */
    public static final String SYNTHETIC = "`syn";

    protected static final String DEFAULT_STRING = "`";

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

    private static final FetchGroup[] EMPTY_FETCH_GROUP_ARRAY
        = new FetchGroup[0];
    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    private MetaDataRepository _repos;
    private transient ClassLoader _loader = null;

    private final ValueMetaData _owner;
    private final LifecycleMetaData _lifeMeta = new LifecycleMetaData(this);
    private File _srcFile = null;
    private String _srcName = null;
    private int _srcType = SRC_OTHER;
    private int _lineNum = 0;
    private int _colNum = 0;
    private String[] _comments = null;
    private int _listIndex = -1;
    private int _srcMode = MODE_META | MODE_MAPPING;
    private int _resMode = MODE_NONE;

    private Class<?> _type = Object.class;
    private int _hashCode = Object.class.getName().hashCode();
    private String _typeString = Object.class.getName();
    private final Map<String,FieldMetaData> _fieldMap = new TreeMap<>();
    private Map<String,FieldMetaData> _supFieldMap = null;
    private boolean _defSupFields = false;
    private Collection<String> _staticFields = null;
    private int[] _fieldDataTable = null;
    private Map<String,FetchGroup> _fgMap = null;

    ////////////////////////////////////////////////////////////////////
    // Note: if you add additional state, make sure to add it to copy()
    ////////////////////////////////////////////////////////////////////

    private Class<?> _objectId = null;
    private Class<?> _idClass = null;
    private Boolean _objectIdShared = null;
    private Boolean _openjpaId = null;
    private Boolean _extent = null;
    private Boolean _embedded = null;
    private boolean _embeddable = false;
    private Boolean _interface = null;
    private Class<?> _impl = null;
    private List<Class<?>> _interfaces = null;
    private final Map<Class<?>,Map<String,String>> _ifaceMap =
    	new HashMap<>();
    private Integer _identity = null;
    private int _idStrategy = ValueStrategies.NONE;
    private int _accessType = AccessCode.UNKNOWN;

    private String _seqName = DEFAULT_STRING;
    private SequenceMetaData _seqMeta = null;
    private String _cacheName = DEFAULT_STRING; // null implies @DataCache(enabled=false)
    private boolean _dataCacheEnabled = false;     // true implies the class has been annotated by the user or name of
                                                // the cache is explicitly set by the user to a null string

    private Boolean _cacheEnabled = null;       // denotes status of JPA 2 @Cacheable annotation
    private int _cacheTimeout = Integer.MIN_VALUE;
    private Boolean _detachable = null;
    private String _detachState = DEFAULT_STRING;
    private String _alias = null;
    private int _versionIdx = Integer.MIN_VALUE;

    private Class<?> _super = null;
    private ClassMetaData _superMeta = null;
    private Class<?>[] _subs = null;
    private ClassMetaData[] _subMetas = null;
    private ClassMetaData[] _mapSubMetas = null;

    private FieldMetaData[] _fields = null;
    private FieldMetaData[] _unmgdFields = null;
    private FieldMetaData[] _allFields = null;
    private FieldMetaData[] _allPKFields = null;
    private FieldMetaData[] _allDFGFields = null;
    private FieldMetaData[] _definedFields = null;
    private FieldMetaData[] _listingFields = null;
    private FieldMetaData[] _allListingFields = null;
    private FieldMetaData[] _allProxyFields = null;
    private FieldMetaData[] _allLrsFields = null;
    private FetchGroup[] _fgs = null;
    private FetchGroup[] _customFGs = null;
    private boolean _intercepting = false;
    private Boolean _useIdClassFromParent = null;
    private boolean _abstract = false;
    private Boolean _hasAbstractPKField = null;
    private Boolean _hasPKFieldsFromAbstractClass = null;
    private int[] _pkAndNonPersistentManagedFmdIndexes = null;
    private Boolean inverseManagedFields = null;
    private List<FieldMetaData> _mappedByIdFields;
    private boolean _mappedByIdFieldsSet = false;
    private boolean _useSchemaElement = true;

    /**
     * Constructor. Supply described type and repository.
     */
    protected ClassMetaData(Class<?> type, MetaDataRepository repos) {
        _repos = repos;
        _owner = null;
        setDescribedType(type);
        registerForValueUpdate("DataCacheTimeout");
    }

    /**
     * Embedded constructor. Supply embedding value.
     */
    protected ClassMetaData(ValueMetaData owner) {
        _owner = owner;
        _repos = owner.getRepository();
        setEnvClassLoader(owner.getFieldMetaData().getDefiningMetaData().
            getEnvClassLoader());
        registerForValueUpdate("DataCacheTimeout");
    }

    /**
     * Return the owning repository.
     */
    @Override
    public MetaDataRepository getRepository() {
        return _repos;
    }

    /**
     * If this metadata is for an embedded object, returning the owning value.
     */
    public ValueMetaData getEmbeddingMetaData() {
        return _owner;
    }

    /**
     * The persistence capable class described by this metadata.
     */
    public Class<?> getDescribedType() {
        return _type;
    }

    /**
     * The persistence capable stringified class described by this metadata.
     */
    public String getDescribedTypeString(){
        return _typeString;
    }

    /**
     * Set the class described by this metadata. The type may be reset when
     * an embedded value changes its declared type.
     */
    protected void setDescribedType(Class<?> type) {
        if (type.getSuperclass() != null && "java.lang.Enum".equals
            (type.getSuperclass().getName()))
            throw new MetaDataException(_loc.get("enum", type));
        _type = type;
        _typeString = _type.getName();
        _hashCode = _typeString.hashCode();
        if (PersistenceCapable.class.isAssignableFrom(type))
            setIntercepting(true);
    }

    /**
     * The environmental loader used when loading this metadata.
     * The class metadata should use this loader when loading metadata for
     * its superclass and field types.
     */
    public ClassLoader getEnvClassLoader() {
        return _loader;
    }

    /**
     * The class environmental loader used when loading this metadata.
     * The class metadata should use this loader when loading metadata for
     * its superclass and field types.
     */
    public void setEnvClassLoader(ClassLoader loader) {
        _loader = loader;
    }

    /**
     * The persistence capable superclass of the described type.
     */
    public Class<?> getPCSuperclass() {
        return _super;
    }

    /**
     * The persistence capable superclass of the described type.
     */
    public void setPCSuperclass(Class<?> pc) {
        clearAllFieldCache();
        _super = pc;
    }

    /**
     * The metadata for this class' superclass.
     */
    public ClassMetaData getPCSuperclassMetaData() {
        if (_superMeta == null && _super != null) {
            if (_owner != null) {
                _superMeta = _repos.newEmbeddedClassMetaData(_owner);
                _superMeta.setDescribedType(_super);
            } else
                _superMeta = _repos.getMetaData(_super, _loader, true);
        }
        return _superMeta;
    }

    /**
     * The metadata for this class' superclass.
     */
    public void setPCSuperclassMetaData(ClassMetaData meta) {
        clearAllFieldCache();
        _superMeta = meta;
        if (meta != null)
            setPCSuperclass(meta.getDescribedType());
    }

    /**
     * Whether this class is mapped to the datastore. By default, only
     * returns false if class is embedded-only, but subclasses might override
     * to allow unmapped other types.
     */
    public boolean isMapped() {
        return _embedded != Boolean.TRUE;
    }

    /**
     * Return the closest mapped superclass.
     */
    public ClassMetaData getMappedPCSuperclassMetaData() {
        ClassMetaData sup = getPCSuperclassMetaData();
        if (sup == null || sup.isMapped())
            return sup;
        return sup.getMappedPCSuperclassMetaData();
    }

    /**
     * Return the known persistence capable subclasses of the described type,
     * or empty array if none or if this is embedded metadata.
     */
    public Class<?>[] getPCSubclasses() {
        if (_owner != null)
            return MetaDataRepository.EMPTY_CLASSES;

        _repos.processRegisteredClasses(_loader);
        if (_subs == null) {
            Collection<Class<?>> subs = _repos.getPCSubclasses(_type);
            _subs = subs.toArray(new Class[subs.size()]);
        }
        return _subs;
    }

    /**
     * Return the metadata for the known persistence capable subclasses of
     * the described type, or empty array if none or if this is embedded
     * metadata.
     */
    public ClassMetaData[] getPCSubclassMetaDatas() {
        if (_owner != null)
            return _repos.EMPTY_METAS;

        Class<?>[] subs = getPCSubclasses(); // checks for new
        if (_subMetas == null) {
            if (subs.length == 0)
                _subMetas = _repos.EMPTY_METAS;
            else {
                ClassMetaData[] metas = _repos.newClassMetaDataArray
                    (subs.length);
                for (int i = 0; i < subs.length; i++)
                    metas[i] = _repos.getMetaData(subs[i], _loader, true);
                _subMetas = metas;
            }
        }
        return _subMetas;
    }

    /**
     * Return all mapped subclasses.
     */
    public ClassMetaData[] getMappedPCSubclassMetaDatas() {
        if (_owner != null)
            return _repos.EMPTY_METAS;

        ClassMetaData[] subs = getPCSubclassMetaDatas(); // checks for new
        if (_mapSubMetas == null) {
            if (subs.length == 0)
                _mapSubMetas = subs;
            else {
                List<ClassMetaData> mapped =
                	new ArrayList<>(subs.length);
                for (ClassMetaData sub : subs)
                    if (sub.isMapped())
                        mapped.add(sub);
                _mapSubMetas = mapped.toArray
                    (_repos.newClassMetaDataArray(mapped.size()));
            }
        }
        return _mapSubMetas;
    }

    /**
     * The type of identity being used. This will be one of:
     * <ul>
     * <li>{@link #ID_UNKNOWN}: unknown identity type</li>
     * <li>{@link #ID_DATASTORE}: identity managed by the data store and
     * independent	of the fields of the instance</li>
     * <li>{@link #ID_APPLICATION}: identity managed by the application and
     * defined by one or more fields of the instance</li>
     * </ul> If unspecified, defaults to {@link #ID_DATASTORE} if there are no
     * primary key fields, and {@link #ID_APPLICATION} otherwise.
     */
    public int getIdentityType() {
    	if (_identity != null) {
    		return _identity;
    	} else {
    		ClassMetaData sup = getPCSuperclassMetaData();
	        if (sup != null && sup.getIdentityType() != ID_UNKNOWN)
	            _identity = sup.getIdentityType();
	        else if (getPrimaryKeyFields().length > 0)
	            _identity = ID_APPLICATION;
	        else if (isMapped())
	            _identity = ID_DATASTORE;
	        else if (isAbstract())
	        	_identity = ID_UNKNOWN;
	        else
	            _identity = _repos.getMetaDataFactory().getDefaults().
	            	 getDefaultIdentityType();
    	}
        return _identity;
    }

    /**
     * The type of identity being used. This will be one of:
     * <ul>
     * <li>{@link #ID_UNKNOWN}: unknown identity type</li>
     * <li>{@link #ID_DATASTORE}: identity managed by the data store and
     * independent	of the fields of the instance</li>
     * <li>{@link #ID_APPLICATION}: identity managed by the application and
     * defined by one or more fields of the instance</li>
     * </ul> If unspecified, defaults to {@link #ID_DATASTORE} if there are no
     * primary key fields, and {@link #ID_APPLICATION} otherwise.
     */
    public void setIdentityType(int type) {
        _identity = type;
        if (type != ID_APPLICATION) {
            _objectId = null;
            _openjpaId = null;
        }
    }

    /**
     * The metadata-specified class to use for the object ID.
     */
    public Class<?> getObjectIdType() {
        // if this entity does not use IdClass from the parent entity,
        // just return the _objectId set during annotation parsing time.
        if (!useIdClassFromParent()) {
            if (_objectId != null)
                return _objectId;
        }

        // if this entity uses IdClass from the parent entity,
        // the _objectId set during the parsing time should be
        // ignored, and let the system determine the objectId type
        // of this entity.
        if (getIdentityType() != ID_APPLICATION)
            return null;
        ClassMetaData sup = getPCSuperclassMetaData();
        if (sup != null && sup.getIdentityType() != ID_UNKNOWN) {
            _objectId = sup.getObjectIdType();
            return _objectId;
        }

        // figure out OpenJPA identity type based on primary key field
        FieldMetaData[] pks = getPrimaryKeyFields();
        if (pks.length != 1)
            return null;
        switch (pks[0].getObjectIdFieldTypeCode()) {
            case JavaTypes.BYTE:
            case JavaTypes.BYTE_OBJ:
                _objectId = ByteId.class;
                break;
            case JavaTypes.CHAR:
            case JavaTypes.CHAR_OBJ:
                _objectId = CharId.class;
                break;
            case JavaTypes.DOUBLE:
            case JavaTypes.DOUBLE_OBJ:
                _objectId = DoubleId.class;
                break;
            case JavaTypes.FLOAT:
            case JavaTypes.FLOAT_OBJ:
                _objectId = FloatId.class;
                break;
            case JavaTypes.INT:
            case JavaTypes.INT_OBJ:
                _objectId = IntId.class;
                break;
            case JavaTypes.LONG:
            case JavaTypes.LONG_OBJ:
                _objectId = LongId.class;
                break;
            case JavaTypes.SHORT:
            case JavaTypes.SHORT_OBJ:
                _objectId = ShortId.class;
                break;
            case JavaTypes.STRING:
                _objectId = StringId.class;
                break;
            case JavaTypes.DATE:
                _objectId = DateId.class;
                break;
            case JavaTypes.OID:
            case JavaTypes.OBJECT:
                _objectId = ObjectId.class;
                break;
            case JavaTypes.BIGDECIMAL:
                _objectId = BigDecimalId.class;
                break;
            case JavaTypes.BIGINTEGER:
                _objectId = BigIntegerId.class;
                break;
            case JavaTypes.BOOLEAN:
            case JavaTypes.BOOLEAN_OBJ:
                _objectId = BooleanId.class;
                break;
            case JavaTypes.UUID_OBJ:
                _objectId = UuidId.class;
                break;
        }
        return _objectId;
    }

    /**
     * The metadata-specified class to use for the object ID.
     * When there is IdClass annotation, AnnotationMetaDataParser
     * will call this method to set ObjectId type. However, if
     * this is a derived identity in the child entity where a
     * relation field (parent entity) is used as an id, and this
     * relation field has an IdClass, the IdClass annotation in
     * the child entity can be ignored as Openjpa will automatically
     * wrap parent's IdClass as child's IdClass.
     */
    public void setObjectIdType(Class<?> cls, boolean shared) {
        _objectId = null;
        _openjpaId = null;
        _objectIdShared = null;
        if (cls != null) {
            // don't let people assign OpenJPAId types; safer to calculate it
            // ourselves
            setIdentityType(ID_APPLICATION);
            if (!OpenJPAId.class.isAssignableFrom(cls)) {
                _objectId = cls;
                _idClass = cls;
                _objectIdShared = (shared) ? Boolean.TRUE : Boolean.FALSE;
            }
        }
    }

    /**
     * Whether this type uses an application identity class that is shared
     * with other classes, and is therefore wrapped in an {@link ObjectId}.
     */
    public boolean isObjectIdTypeShared() {
        if (_objectIdShared != null)
            return _objectIdShared;
        if (_super != null)
            return getPCSuperclassMetaData().isObjectIdTypeShared();
        return isOpenJPAIdentity();
    }

    /**
     * Whether this type uses OpenJPA identity.
     */
    public boolean isOpenJPAIdentity() {
        if (_openjpaId == null) {
            Class<?> cls = getObjectIdType();
            if (cls == null)
                return false;
            _openjpaId = (OpenJPAId.class.isAssignableFrom(cls)) ? Boolean.TRUE
                : Boolean.FALSE;
        }
        return _openjpaId;
    }

    /**
     * The strategy to use for datastore identity generation.
     * One of the constants from {@link ValueStrategies}.
     */
    public int getIdentityStrategy() {
        if (getIdentityType() == ID_DATASTORE
            && _idStrategy == ValueStrategies.NONE) {
            ClassMetaData sup = getPCSuperclassMetaData();
            if (sup != null && sup.getIdentityType() != ID_UNKNOWN)
                _idStrategy = sup.getIdentityStrategy();
            else
                _idStrategy = ValueStrategies.NATIVE;
        }
        return _idStrategy;
    }

    /**
     * The strategy to use for datastore identity generation.
     * One of the constants from {@link ValueStrategies}.
     */
    public void setIdentityStrategy(int strategy) {
        _idStrategy = strategy;
        if (strategy != ValueStrategies.SEQUENCE)
            setIdentitySequenceName(null);
    }

    /**
     * The datastore identity sequence name, or null for none.
     */
    public String getIdentitySequenceName() {
        if (DEFAULT_STRING.equals(_seqName)) {
            if (_super != null)
                _seqName = getPCSuperclassMetaData().getIdentitySequenceName();
            else
                _seqName = null;
        }
        return _seqName;
    }

    /**
     * The datastore identity sequence name, or null for none.
     */
    public void setIdentitySequenceName(String seqName) {
        _seqName = seqName;
        _seqMeta = null;
        if (seqName != null)
            setIdentityStrategy(ValueStrategies.SEQUENCE);
    }

    /**
     * Metadata for the datastore identity sequence.
     */
    public SequenceMetaData getIdentitySequenceMetaData() {
        if (_seqMeta == null && getIdentitySequenceName() != null)
            _seqMeta = _repos.getSequenceMetaData(this,
                getIdentitySequenceName(), true);
        return _seqMeta;
    }

    /**
     * Information about lifecycle callbacks for this class.
     */
    public LifecycleMetaData getLifecycleMetaData() {
        return _lifeMeta;
    }

    /**
     * Returns the alias for the described type, or <code>null</code> if none
     * has been set.
     *
     * @see #setTypeAlias
     */
    public String getTypeAlias() {
        if (_alias == null)
            _alias = ClassUtil.getClassName(_type);
        return _alias;
    }

    /**
     * Sets the alias for the described type. The alias can be
     * any arbitrary string that the implementation can later use to
     * refer to the class. Note that at runtime, only the alias
     * computed when the persistent type was enhanced is used.
     *
     * @param alias the alias name to apply to the described type
     */
    public void setTypeAlias(String alias) {
        _alias = alias;
    }

    /**
     * The access type used by this class.
     *
     */
    public int getAccessType() {
        return _accessType;
    }

    /**
     * Sets the access type.
     */
    public void setAccessType(int type) {
        if (type == _accessType || type == AccessCode.UNKNOWN)
            return;
        if (!AccessCode.isValidClassCode(type)) {
            throw new IllegalArgumentException(_loc.get("access-type-invalid",
                                                        this, AccessCode.toClassString(type)).getMessage());
        }
        if (_accessType != AccessCode.UNKNOWN) { // changing access type
            _repos.getLog().trace(_loc.get("access-type-change",
                                           this, AccessCode.toClassString(type),
                                           AccessCode.toClassString(_accessType)).getMessage());
        }
        _accessType = type;
    }

    /**
     * Asserts the given field (which must belong to this receiver)
     * can be set to the given access code. If the field code is allowed,
     * it may have the side effect of changing the access code of this receiver.
     */
    void mergeFieldAccess(FieldMetaData fmd, int fCode) {
        setAccessType(AccessCode.mergeFieldCode(this, fmd, fCode));
    }

    /**
     * Affirms if access style is explicitly defined.
     */
    public boolean isExplicitAccess() {
        return AccessCode.isExplicit(_accessType);
    }

    /**
     * Affirms if attributes of this class use mixed access types.
     */
    public boolean isMixedAccess() {
        return AccessCode.isMixed(_accessType);
    }

    /**
     * Whether the type requires extent management.
     */
    public boolean getRequiresExtent() {
        if (_owner != null || isEmbeddedOnly())
            return false;

        if (_extent == null) {
            ClassMetaData sup = getPCSuperclassMetaData();
            if (sup != null)
                _extent = (sup.getRequiresExtent()) ? Boolean.TRUE
                    : Boolean.FALSE;
            else
                _extent = Boolean.TRUE;
        }
        return _extent;
    }

    /**
     * Whether the type requires extent management.
     */
    public void setRequiresExtent(boolean req) {
        _extent = (req) ? Boolean.TRUE : Boolean.FALSE;
    }

    /**
     * Whether the type can only be used as an embedded object.
     */
    public boolean isEmbeddedOnly() {
        if (_embedded == null) {
            ClassMetaData sup = getPCSuperclassMetaData();
            if (sup != null)
                _embedded = (sup.isEmbeddedOnly()) ? Boolean.TRUE
                    : Boolean.FALSE;
            else
                _embedded = Boolean.FALSE;
        }
        return _embedded;
    }

    /**
     * Whether the type can only be used as an embedded object.
     */
    public void setEmbeddedOnly(boolean embed) {
        _embedded = (embed) ? Boolean.TRUE : Boolean.FALSE;
    }

    public boolean isEmbeddable() {
        return _embeddable;
    }

    public void setEmbeddable() {
        _embeddable = true;
    }

    /**
     * Whether the type's fields are actively intercepted, either by
     * redefinition or enhancement.
     */
    public boolean isIntercepting() {
        return _intercepting;
    }

    /**
     * Whether the type's fields are actively intercepted, either by
     * redefinition or enhancement.
     */
    public void setIntercepting(boolean intercepting) {
        _intercepting = intercepting;
    }

    /**
     * Whether the type is a managed interface.
     */
    public boolean isManagedInterface() {
        if (!_type.isInterface())
            return false;
        return _interface == null ? false : _interface;
    }

    /**
     * Whether the type is a managed interface
     */
    public void setManagedInterface(boolean managedInterface) {
        if (!_type.isInterface())
            throw new MetaDataException(_loc.get("not-interface", _type));
        _interface = managedInterface ? Boolean.TRUE : Boolean.FALSE;

        // managed interfaces always do proper interception; OpenJPA generates
        // the implementations.
        if (isManagedInterface())
            setIntercepting(true);

        // managed interfaces always use property access.
        setAccessType(AccessCode.PROPERTY);
    }

    /**
     * Return the managed interface implementor if any.
     */
    public Class<?> getInterfaceImpl() {
        return _impl;
    }

    /**
     * Set the managed interface implementor class.
     */
    public void setInterfaceImpl(Class<?> impl) {
        _impl = impl;
    }

    /**
     * Return all explicitly declared interfaces this class implements.
     */
    public Class<?>[] getDeclaredInterfaces() {
        if (_interfaces == null)
            return MetaDataRepository.EMPTY_CLASSES;
        return _interfaces.toArray(new Class[_interfaces.size()]);
    }

    /**
     * Explicitly declare the given interface among the ones this
     * class implements.
     */
    public void addDeclaredInterface(Class<?> iface) {
        if (iface == null || !iface.isInterface())
            throw new MetaDataException(_loc.get("declare-non-interface",
                this, iface));
        if (_interfaces == null)
            _interfaces = new ArrayList<>();
        _interfaces.add(iface);
    }

    /**
     * Remove the given interface from the declared list.
     */
    public boolean removeDeclaredInterface(Class<?> iface) {
        if (_interfaces == null)
            return false;
        return _interfaces.remove(iface);
    }

    /**
     * Alias properties from the given interface during  queries to
     * the local field.
     */
    public void setInterfacePropertyAlias(Class<?> iface, String orig,
        String local) {
        synchronized (_ifaceMap) {
            Map<String, String> fields = _ifaceMap.computeIfAbsent(iface, k -> new HashMap<>());
            if (fields.containsKey(orig))
                throw new MetaDataException(_loc.get("duplicate-iface-alias",
                    this, orig, local));
            fields.put(orig, local);
        }
    }

    /**
     * Get local field alias for the given interface property.
     */
    public String getInterfacePropertyAlias(Class<?> iface, String orig) {
        synchronized (_ifaceMap) {
            Map<String,String> fields = _ifaceMap.get(iface);
            if (fields == null)
                return null;
            return fields.get(orig);
        }
    }

    /**
     * Return all aliases property named for the given interface.
     */
    public String[] getInterfaceAliasedProperties(Class<?> iface) {
        synchronized (_ifaceMap) {
            Map<String,String> fields = _ifaceMap.get(iface);
            if (fields == null)
                return EMPTY_STRING_ARRAY;
            return fields.keySet().toArray(new String[fields.size()]);
        }
    }

    /**
     * Return the number of fields that use impl or intermediate data, in
     * order to create a compacted array for storage of said data.
     */
    public int getExtraFieldDataLength() {
        int[] table = getExtraFieldDataTable();
        for (int i = table.length - 1; i >= 0; i--)
            if (table[i] != -1)
                return table[i] + 1;
        return 0;
    }

    /**
     * Return the intermediate field data index of the given field
     * in the compacted array, or -1 if the field does not use extra data.
     *
     * @see #getExtraFieldDataLength
     */
    public int getExtraFieldDataIndex(int field) {
        int[] array = getExtraFieldDataTable();
        if (field < 0 || field >= array.length)
            return -1;
        return array[field];
    }

    /**
     * Creates a table mapping each field index to its extra data index.
     */
    private int[] getExtraFieldDataTable() {
        if (_fieldDataTable == null) {
            FieldMetaData[] fmds = getFields();
            int[] table = new int[fmds.length];
            int idx = 0;
            for (int i = 0; i < fmds.length; i++) {
                if (fmds[i].usesIntermediate()
