/*
 * 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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.JavaVersions;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.lib.util.collections.ComparatorChain;
import org.apache.openjpa.lib.xml.Commentable;
import org.apache.openjpa.util.Exceptions;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.ProxyManager;
import org.apache.openjpa.util.UnsupportedException;
import org.apache.openjpa.util.UserException;


/**
 * Metadata for a managed class field.
 *
 * @author Abe White
 */
public class FieldMetaData
    extends Extensions
    implements ValueMetaData, MetaDataContext, MetaDataModes, Commentable {

    private static final long serialVersionUID = -566180883009883198L;

    /**
     * Constant specifying that no null-value was given.
     */
    public static final int NULL_UNSET = -1;

    /**
     * Constant specifying to use a datastore null to persist null values
     * in object fields.
     */
    public static final int NULL_NONE = 0;

    /**
     * Constant specifying to use a datastore default value to persist null
     * values in object fields.
     */
    public static final int NULL_DEFAULT = 1;

    /**
     * Constant specifying to throw an exception when attempting to persist
     * null values in object fields.
     */
    public static final int NULL_EXCEPTION = 2;

    /**
     * Constant specifying the management level of a field.
     */
    public static final int MANAGE_PERSISTENT = 3;

    /**
     * Constant specifying the management level of a field.
     */
    public static final int MANAGE_TRANSACTIONAL = 1;

    /**
     * Constant specifying the management level of a field.
     */
    public static final int MANAGE_NONE = 0;

    public static final int ONE_TO_ONE = 1;
    public static final int ONE_TO_MANY = 2;
    public static final int MANY_TO_ONE = 3;
    public static final int MANY_TO_MANY = 4;

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

    private static final int DFG_FALSE = 1;
    private static final int DFG_TRUE = 2;
    private static final int DFG_EXPLICIT = 4;

    private static final Method DEFAULT_METHOD;
    static {
        try {
            DEFAULT_METHOD = Object.class.getMethod("wait", (Class[]) null);
        } catch (Exception e) {
            // shouldn't ever happen
            throw new InternalException(e);
        }
    }

    // name and type
    private final ValueMetaData _val;
    private final ValueMetaData _key;
    private final ValueMetaData _elem;
    private final ClassMetaData _owner;
    private final String _name;
    private Class<?> _dec = null;
    private ClassMetaData _decMeta = null;
    private String _fullName = null;
    private String _embedFullName = null;
    private int _resMode = MODE_NONE;
    private String _mappedByIdValue = null;
    private int _access = AccessCode.UNKNOWN;

    // load/store info
    private String[] _comments = null;
    private int _listIndex = -1;

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

    // misc info
    private Class<?> _proxyClass = null;
    private Object _initializer = null;
    private boolean _transient = false;
    private boolean _primKey = false;
    private Boolean _version = null;
    private int _nullValue = NULL_UNSET;
    private int _manage = MANAGE_PERSISTENT;
    private int _index = -1;
    private int _decIndex = -1;
    private int _pkIndex = -1;
    private boolean _explicit = false;
    private int _dfg = 0;
    private Set<String> _fgSet = null;
    private String[] _fgs = null;
    private String   _lfg = null;
    private Boolean _lrs = null;
    private Boolean _stream = null;
    private String _extName = null;
    private String _factName = null;
    private String _extString = null;
    private Map _extValues = Collections.EMPTY_MAP;
    private Map _fieldValues = Collections.EMPTY_MAP;
    private Boolean _enumField = null;
    private Boolean _lobField = null;
    private Boolean _serializableField = null;
    private boolean _generated = false;
    private boolean _useSchemaElement = true;
    private Class _converter;

    // Members aren't serializable. Use a proxy that can provide a Member
    // to avoid writing the full Externalizable implementation.
    private MemberProvider _backingMember = null;

    // Members aren't serializable. Initializing _extMethod and _factMethod to
    // DEFAULT_METHOD is sufficient to trigger lazy population of these fields.
    private transient Method _extMethod = DEFAULT_METHOD;
    private transient Member _factMethod = DEFAULT_METHOD;

    private transient Constructor _converterConstructor;
    private transient Method _converterExtMethod;
    private transient Method _converterFactMethod;
    
    // intermediate and impl data
    private boolean _intermediate = true;
    private Boolean _implData = Boolean.TRUE;

    // value generation
    private int _valStrategy = -1;
    private int _upStrategy = -1;
    private String _seqName = ClassMetaData.DEFAULT_STRING;
    private SequenceMetaData _seqMeta = null;

    // inverses
    private String _mappedBy = null;
    private FieldMetaData _mappedByMeta = null;
    private FieldMetaData[] _inverses = null;
    private String _inverse = ClassMetaData.DEFAULT_STRING;

    // ordering on load
    private Order[] _orders = null;
    private String _orderDec = null;
    // indicate if this field is used by other field as "order by" value
    private boolean _usedInOrderBy = false;
    private boolean _isElementCollection = false;
    private int _associationType;

    private boolean _persistentCollection = false;

    private Boolean _delayCapable = null;
    /**
     * Constructor.
     *
     * @param name the field name
     * @param type the field type
     * @param owner the owning class metadata
     */
    protected FieldMetaData(String name, Class<?> type, ClassMetaData owner) {
        _name = name;
        _owner = owner;
        _dec = null;
        _decMeta = null;
        _val = owner.getRepository().newValueMetaData(this);
        _key = owner.getRepository().newValueMetaData(this);
        _elem = owner.getRepository().newValueMetaData(this);

        setDeclaredType(type);
    }

    /**
     * Supply the backing member object; this allows us to utilize
     * parameterized type information if available.
     * Sets the access style of this receiver based on whether the given
     * member represents a field or getter method.
     */
    public void backingMember(Member member) {
        if (member == null)
            return;
        if (Modifier.isTransient(member.getModifiers()))
            _transient = true;

        _backingMember = new MemberProvider(member);

        Class<?> type;
        Class<?>[] types;
        if (member instanceof Field) {
            Field f = (Field) member;
            type = f.getType();
            types = JavaVersions.getParameterizedTypes(f);
            setAccessType(AccessCode.FIELD);
        } else {
            Method meth = (Method) member;
            type = meth.getReturnType();
            types = JavaVersions.getParameterizedTypes(meth);
            setAccessType(AccessCode.PROPERTY);
        }

        setDeclaredType(type);
        if (Collection.class.isAssignableFrom(type)
            && _elem.getDeclaredType() == Object.class
            && types.length == 1) {
            _elem.setDeclaredType(types[0]);
        } else if (Map.class.isAssignableFrom(type)
            && types.length == 2) {
            if (_key.getDeclaredType() == Object.class)
                _key.setDeclaredType(types[0]);
            if (_elem.getDeclaredType() == Object.class)
                _elem.setDeclaredType(types[1]);
        }
    }

    /**
     * Return the backing member supplied in {@link #backingMember}.
     */
    public Member getBackingMember() {
        return (_backingMember == null) ? null : _backingMember.getMember();
    }

    /**
     * The metadata repository.
     */
    @Override
    public MetaDataRepository getRepository() {
        return _owner.getRepository();
    }

    /**
     * The class that defines the metadata for this field.
     */
    public ClassMetaData getDefiningMetaData() {
        return _owner;
    }

    /**
     * The declaring class.
     */
    public Class<?> getDeclaringType() {
        return (_dec == null) ? _owner.getDescribedType() : _dec;
    }

    /**
     * The declaring class.
     */
    public void setDeclaringType(Class<?> cls) {
        _dec = cls;
        _decMeta = null;
        _fullName = null;
        _embedFullName = null;
    }

    /**
     * The declaring class.
     */
    public ClassMetaData getDeclaringMetaData() {
        if (_dec == null)
            return _owner;
        if (_decMeta == null)
            _decMeta = getRepository().getMetaData(_dec,
                _owner.getEnvClassLoader(), true);
        return _decMeta;
    }

    /**
     * The field name.
     */
    public String getName() {
        return _name;
    }

    /**
     * The field name, qualified by the owning class.
     * @deprecated Use getFullName(boolean) instead.
     */
    @Deprecated
    public String getFullName() {
        return getFullName(false);
    }

    /**
     * The field name, qualified by the owning class and optionally the
     * embedding owner's name (if any).
     */
    public String getFullName(boolean embedOwner) {
        if (_fullName == null)
            _fullName = getDeclaringType().getName() + "." + _name;
        if (embedOwner && _embedFullName == null) {
            if (_owner.getEmbeddingMetaData() == null)
                _embedFullName = _fullName;
            else
                _embedFullName = _owner.getEmbeddingMetaData().
                    getFieldMetaData().getFullName(true) + "." + _fullName;
        }
        return (embedOwner) ? _embedFullName : _fullName;
    }

    /**
     * The field name, qualified by the defining class.
     */
    public String getRealName() {
    	// Added to support OPENJPA-704
        return getDefiningMetaData().getDescribedType().getName() + "." + _name;
    }

    /**
     * MetaData about the field value.
     */
    public ValueMetaData getValue() {
        return _val;
    }

    /**
     * Metadata about the key value.
     */
    public ValueMetaData getKey() {
        return _key;
    }

    /**
     * Metadata about the element value.
     */
    public ValueMetaData getElement() {
        return _elem;
    }

    /**
     * Return whether this field is mapped to the datastore. By default,
     * returns true for all persistent fields whose defining class is mapped.
     */
    public boolean isMapped() {
        return _manage == MANAGE_PERSISTENT && _owner.isMapped();
    }

    /**
     * The type this field was initialized with, and therefore the
     * type to use for proxies when loading data into this field.
     */
    public Class<?> getProxyType() {
        return (_proxyClass == null) ? getDeclaredType() : _proxyClass;
    }

    /**
     * The type this field was initialized with, and therefore the
     * type to use for proxies when loading data into this field.
     */
    public void setProxyType(Class<?> type) {
        _proxyClass = type;
    }

    /**
     * The initializer used by the field, or null if none. This
     * is additional information for initializing the field, such as
     * a custom {@link Comparator} used by a {@link Set} or
     * a {@link TimeZone} used by a {@link Calendar}.
     */
    public Object getInitializer() {
        return _initializer;
    }

    /**
     * The initializer used by the field, or null if none. This
     * is additional information for initializing the field, such as
     * a custom {@link Comparator} used by a {@link Set} or
     * a {@link TimeZone} used by a {@link Calendar}.
     */
    public void setInitializer(Object initializer) {
        _initializer = initializer;
    }

    /**
     * Return whether this is a transient field.
     */
    public boolean isTransient() {
        return _transient;
    }

    /**
     * Return whether this is a transient field.
     */
    public void setTransient(boolean trans) {
        _transient = trans;
    }

    /**
     * The absolute index of this persistent/transactional field.
     */
    public int getIndex() {
        return _index;
    }

    /**
     * The absolute index of this persistent/transactional field.
     */
    public void setIndex(int index) {
        _index = index;
    }

    /**
     * The relative index of this persistent/transactional field.
     */
    public int getDeclaredIndex() {
        return _decIndex;
    }

    /**
     * The relative index of this persistent/transactional field.
     */
    public void setDeclaredIndex(int index) {
        _decIndex = index;
    }

    /**
     * The index in which this field was listed in the metadata. Defaults to
     * <code>-1</code> if this field was not listed in the metadata.
     */
    public int getListingIndex() {
        return _listIndex;
    }

    /**
     * The index in which this field was listed in the metadata. Defaults to
     * <code>-1</code> if this field was not listed in the metadata.
     */
    public void setListingIndex(int index) {
        _listIndex = index;
    }

    /**
     * The absolute primary key index for this field, or -1 if not a primary
     * key. The first primary key field has index 0, the second index 1, etc.
     */
    public int getPrimaryKeyIndex() {
        return _pkIndex;
    }

    /**
     * The absolute primary key index for this field, or -1 if not a primary
     * key. The first primary key field has index 0, the second index 1, etc.
     */
    public void setPrimaryKeyIndex(int index) {
        _pkIndex = index;
    }

    /**
     * Return the management level for the field. Will be one of:
     * <ul>
     * <li>{@link #MANAGE_PERSISTENT}: the field is persistent</li>
     * <li>{@link #MANAGE_TRANSACTIONAL}: the field is transactional but not
     * persistent</li>
     * <li>{@link #MANAGE_NONE}: the field is not managed</li>
     * </ul> Defaults to {@link #MANAGE_PERSISTENT}.
     */
    public int getManagement() {
        return _manage;
    }

    /**
     * Return the management level for the field. Will be one of:
     * <ul>
     * <li>{@link #MANAGE_PERSISTENT}: the field is persistent</li>
     * <li>{@link #MANAGE_TRANSACTIONAL}: the field is transactional but not
     * persistent</li>
     * <li>{@link #MANAGE_NONE}: the field is not managed</li>
     * </ul>
     * Defaults to {@link #MANAGE_PERSISTENT}.
     */
    public void setManagement(int manage) {
        if ((_manage == MANAGE_NONE) != (manage == MANAGE_NONE))
            _owner.clearFieldCache();
        _manage = manage;
    }

    /**
     * Whether this is a primary key field.
     */
    public boolean isPrimaryKey() {
        return _primKey;
    }

    /**
     * Whether this is a primary key field.
     */
    public void setPrimaryKey(boolean primKey) {
        _primKey = primKey;
    }

    /**
     * For a primary key field, return the type of the corresponding object id
     * class field.
     */
    public int getObjectIdFieldTypeCode() {
        ClassMetaData relmeta = getDeclaredTypeMetaData();
        if (relmeta == null)
            return getDeclaredTypeCode();
        if (relmeta.getIdentityType() == ClassMetaData.ID_DATASTORE) {
            boolean unwrap = getRepository().getMetaDataFactory().getDefaults().
                isDataStoreObjectIdFieldUnwrapped();
            return (unwrap) ? JavaTypes.LONG : JavaTypes.OBJECT;
        }
        if (relmeta.isOpenJPAIdentity())
            return relmeta.getPrimaryKeyFields()[0].getObjectIdFieldTypeCode();
        return JavaTypes.OBJECT;
    }

    /**
     * For a primary key field, return the type of the corresponding object id
     * class field.
     */
    public Class<?> getObjectIdFieldType() {
        ClassMetaData relmeta = getDeclaredTypeMetaData();
        if (relmeta == null || getValue().isEmbedded())
            return getDeclaredType();
        switch (relmeta.getIdentityType()) {
            case ClassMetaData.ID_DATASTORE:
                boolean unwrap = getRepository().getMetaDataFactory().
                    getDefaults().isDataStoreObjectIdFieldUnwrapped();
                return (unwrap) ? long.class : Object.class;
            case ClassMetaData.ID_APPLICATION:
                if (relmeta.isOpenJPAIdentity())
                    return relmeta.getPrimaryKeyFields()[0].
                        getObjectIdFieldType();
                return (relmeta.getObjectIdType() == null) ? Object.class
                    : relmeta.getObjectIdType();
            default:
                return Object.class;
        }
    }

    /**
     * Whether this field holds optimistic version information.
     */
    public boolean isVersion() {
        return _version == Boolean.TRUE;
    }

    /**
     * Whether this field holds optimistic version information.
     */
    public void setVersion(boolean version) {
        _version = (version) ? Boolean.TRUE : Boolean.FALSE;
    }

    /**
     * Whether this field is in the default fetch group.
     */
    public boolean isInDefaultFetchGroup() {
        if (_dfg == 0) {
            if (_manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion())
                _dfg = DFG_FALSE;
            else {
                // field left as default; dfg setting depends on type
                switch (getTypeCode()) {
                    case JavaTypes.OBJECT:
                        if (isSerializable() || isEnum())
                            _dfg = DFG_TRUE;
                        else
                            _dfg = DFG_FALSE;
                        break;
                    case JavaTypes.ARRAY:
                        if (isLobArray())
                            _dfg = DFG_TRUE;
                        else
                            _dfg = DFG_FALSE;
                        break;
                    case JavaTypes.COLLECTION:
                    case JavaTypes.MAP:
                    case JavaTypes.PC:
                    case JavaTypes.PC_UNTYPED:
                        _dfg = DFG_FALSE;
                        break;
                    default:
                        _dfg = DFG_TRUE;
                }
            }
        }
        return (_dfg & DFG_TRUE) > 0;
    }

    private boolean isEnum() {
        if (_enumField == null) {
            Class<?> decl = getDeclaredType();
            _enumField =  Enum.class.isAssignableFrom(decl)
                ? Boolean.TRUE : Boolean.FALSE;
        }
        return _enumField;
    }

    private boolean isSerializable() {
        if (_serializableField == null) {
            Class<?> decl = getDeclaredType();
            if (Serializable.class.isAssignableFrom(decl))
                _serializableField = Boolean.TRUE;
            else
                _serializableField = Boolean.FALSE;
        }
        return _serializableField;
    }

    private boolean isLobArray() {
        // check for byte[], Byte[], char[], Character[]
        if (_lobField == null) {
            Class<?> decl = getDeclaredType();
            if (decl == byte[].class || decl == Byte[].class ||
                decl == char[].class || decl == Character[].class)
                _lobField = Boolean.TRUE;
            else
                _lobField = Boolean.FALSE;
        }
        return _lobField;
    }

    /**
     * Whether this field is in the default fetch group.
     */
    public void setInDefaultFetchGroup(boolean dfg) {
        if (dfg)
            _dfg = DFG_TRUE;
        else
            _dfg = DFG_FALSE;
        _dfg |= DFG_EXPLICIT;
    }

    /**
     * Whether the default fetch group setting is explicit.
     */
    public boolean isDefaultFetchGroupExplicit() {
        return (_dfg & DFG_EXPLICIT) > 0;
    }

    /**
     * Whether the default fetch group setting is explicit. Allow setting
     * for testing.
     */
    public void setDefaultFetchGroupExplicit(boolean explicit) {
        if (explicit)
            _dfg |= DFG_EXPLICIT;
        else
            _dfg &= ~DFG_EXPLICIT;
    }

    /**
     * Gets the name of the custom fetch groups those are associated to this
     * receiver.  This does not include the "default" and "all" fetch groups.
     *
     * @return the set of fetch group names, not including the default and
     * all fetch groups.
     */
    public String[] getCustomFetchGroups() {
        if (_fgs == null) {
            if (_fgSet == null || _manage != MANAGE_PERSISTENT
                || isPrimaryKey() || isVersion())
                _fgs = new String[0];
            else
                _fgs = _fgSet.toArray(new String[_fgSet.size()]);
        }
        return _fgs;
    }

    /**
     * The fetch group that is to be loaded when this receiver is loaded, or
     * null if none set.
     */
    public String getLoadFetchGroup () {
    	return _lfg;
    }

    /**
     * The fetch group that is to be loaded when this receiver is loaded, or
     * null if none set.
     */
    public void setLoadFetchGroup (String lfg) {
        if ("".equals(lfg))
            lfg = null;
    	_lfg = lfg;
    }

    /**
     * Whether this field is in the given fetch group.
     */
    public boolean isInFetchGroup(String fg) {
        if (_manage != MANAGE_PERSISTENT || isPrimaryKey() || isVersion())
            return false;
        if (FetchGroup.NAME_ALL.equals(fg))
            return true;
        if (FetchGroup.NAME_DEFAULT.equals(fg))
            return isInDefaultFetchGroup();
        return _fgSet != null && _fgSet.contains(fg);
    }

    /**
     * Set whether this field is in the given fetch group.
     *
     * @param fg is the name of a fetch group that must be present in the
     * class that declared this field or any of its persistent superclasses.
     */
    public void setInFetchGroup(String fg, boolean in) {
        if (StringUtil.isEmpty(fg))
            throw new MetaDataException(_loc.get("empty-fg-name", this));
        if (fg.equals(FetchGroup.NAME_ALL))
            return;
        if (fg.equals(FetchGroup.NAME_DEFAULT)) {
            setInDefaultFetchGroup(in);
            return;
        }
        if (_owner.getFetchGroup(fg) == null)
            throw new MetaDataException(_loc.get("unknown-fg", fg, this));
        if (in && _fgSet == null)
            _fgSet = new HashSet<>();
        if ((in && _fgSet.add(fg))
            || (!in && _fgSet != null && _fgSet.remove(fg)))
            _fgs = null;
    }

    /**
     * How the data store should treat null values for this field:
     * <ul>
     * <li>{@link #NULL_UNSET}: no value supplied</li>
     * <li>{@link #NULL_NONE}: leave null values as null in the data store</li>
     * <li>{@link #NULL_EXCEPTION}: throw an exception if this field is null
     * at commit</li>
     * <li>{@link #NULL_DEFAULT}: use the database default if this field is
     * null at commit</li>
     * </ul> Defaults to {@link #NULL_UNSET}.
     */
    public int getNullValue() {
        return _nullValue;
    }

    /**
     * How the data store should treat null values for this field:
     * <ul>
     * <li>{@link #NULL_UNSET}: no value supplied</li>
     * <li>{@link #NULL_NONE}: leave null values as null in the data store</li>
     * <li>{@link #NULL_EXCEPTION}: throw an exception if this field is null
     * at commit</li>
     * <li>{@link #NULL_DEFAULT}: use the database default if this field is
     * null at commit</li>
     * </ul> Defaults to {@link #NULL_UNSET}.
     */
    public void setNullValue(int nullValue) {
        _nullValue = nullValue;
    }

    /**
     * Whether this field is explicitly declared in the metadata.
     */
    public boolean isExplicit() {
        return _explicit;
    }

    /**
     * Whether this field is explicitly declared in the metadata.
     */
    public void setExplicit(boolean explicit) {
        _explicit = explicit;
    }

    /**
     * The field that this field shares a mapping with.
     */
    public String getMappedBy() {
        return _mappedBy;
    }

    /**
     * The field that this field shares a mapping with.
     */
    public void setMappedBy(String mapped) {
        _mappedBy = mapped;
        _mappedByMeta = null;
    }

    /**
     * The field that this field shares a mapping with.
     */
    public FieldMetaData getMappedByMetaData() {
        if (_mappedBy != null && _mappedByMeta == null) {
            ClassMetaData meta = null;
            switch (getTypeCode()) {
                case JavaTypes.PC:
                    meta = getTypeMetaData();
                    break;
                case JavaTypes.ARRAY:
                case JavaTypes.COLLECTION:
                case JavaTypes.MAP:
                    meta = _elem.getTypeMetaData();
                    break;
            }

            FieldMetaData field = (meta == null) ? null
                : getMappedByField(meta, _mappedBy);
            if (field == null)
                throw new MetaDataException(_loc.get("no-mapped-by", this,
                    _mappedBy));
            if (field.getMappedBy() != null)
                throw new MetaDataException(_loc.get("circ-mapped-by", this,
                    _mappedBy));
            OpenJPAConfiguration conf = getRepository().getConfiguration();
            boolean isAbstractMappingUniDirectional = getRepository().getMetaDataFactory().
                    getDefaults().isAbstractMappingUniDirectional(conf);
            if (isAbstractMappingUniDirectional) {
                if (field.getDeclaringMetaData().isAbstract())
                    throw new MetaDataException(_loc.get("no-mapped-by-in-mapped-super", field,
                            field.getDeclaringMetaData()));

                if (this.getDeclaringMetaData().isAbstract())
                    throw new MetaDataException(_loc.get("no-mapped-by-in-mapped-super", this,
                            this.getDeclaringMetaData()));
            }
            _mappedByMeta = field;
        }
        return _mappedByMeta;
    }

    public FieldMetaData getMappedByField(ClassMetaData meta, String mappedBy) {
        FieldMetaData field = meta.getField(mappedBy);
        if (field != null)
            return field;
        int dotIdx = mappedBy.indexOf(".");
        if ( dotIdx == -1)
            return null;
        String fieldName = mappedBy.substring(0, dotIdx);
        FieldMetaData field1 = meta.getField(fieldName);
        if (field1 == null)
            return null;
        ClassMetaData meta1 = field1.getEmbeddedMetaData();
        if (meta1 == null)
            return null;
        String mappedBy1 = mappedBy.substring(dotIdx + 1);
        return getMappedByField(meta1, mappedBy1);
    }


    /**
     * Logical inverse field.
     */
    public String getInverse() {
        if (ClassMetaData.DEFAULT_STRING.equals(_inverse))
            _inverse = null;
        return _inverse;
    }

    /**
     * Logical inverse field.
     */
    public void setInverse(String inverse) {
        _inverses = null;
        _inverse = inverse;
    }

    /**
     * Return all inverses of this field.
     */
    public FieldMetaData[] getInverseMetaDatas() {
        if (_inverses == null) {
            // can't declare both an inverse owner and a logical inverse
            String inv = getInverse();
            if (_mappedBy != null && inv != null && !_mappedBy.equals(inv))
                throw new MetaDataException(_loc.get("mapped-not-inverse",
                    this));

            // get the metadata for the type on the other side of this relation
            ClassMetaData meta = null;
            switch (getTypeCode()) {
                case JavaTypes.PC:
                    meta = getTypeMetaData();
                    break;
                case JavaTypes.ARRAY:
                case JavaTypes.COLLECTION:
                case JavaTypes.MAP:
                    meta = _elem.getTypeMetaData();
                    break;
            }

            Collection<FieldMetaData> inverses = null;
            if (meta != null) {
                // add mapped by and named inverse, if any
                FieldMetaData field = getMappedByMetaData();
                if (field != null) {
                    // mapped by field isn't necessarily a pc type, but all
                    // inverses must be
                    if (field.getTypeCode() == JavaTypes.PC
                        || field.getElement().getTypeCode() == JavaTypes.PC) {
                        inverses = new ArrayList<>(3);
                        inverses.add(field);
                    }
                } else if (inv != null) {
                    field = meta.getField(inv);
                    if (field == null)
                        throw new MetaDataException(_loc.get("no-inverse",
                            this, inv));
                    inverses = new ArrayList<>(3);
                    inverses.add(field);
                }

                // scan rel type for fields that name this field as an inverse
                FieldMetaData[] fields = meta.getFields();
                Class<?> type = getDeclaringMetaData().getDescribedType();
                for (FieldMetaData fieldMetaData : fields) {
                    // skip fields that aren't compatible with our owning class
                    switch (fieldMetaData.getTypeCode()) {
                        case JavaTypes.PC:
                            if (!type.isAssignableFrom(fieldMetaData.getType()))
                                continue;
                            break;
                        case JavaTypes.COLLECTION:
                        case JavaTypes.ARRAY:
                            if (!type.isAssignableFrom(fieldMetaData.
                                    getElement().getType()))
                                continue;
                            break;
                        default:
                            continue;
                    }

                    // if the field declares us as its inverse and we haven't
                    // already added it (we might have if we also declared it
                    // as our inverse), add it now
