/*
 * 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.Serializable;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.DynamicPersistenceCapable;
import org.apache.openjpa.enhance.PCEnhancer;
import org.apache.openjpa.enhance.PCRegistry;
import org.apache.openjpa.enhance.PCRegistry.RegisterClassListener;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.event.LifecycleEventManager;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
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.Closeable;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.MultiClassLoader;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringDistance;
import org.apache.openjpa.util.ClassResolver;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.OpenJPAId;


/**
 * Repository of and factory for persistent metadata.
 *
 * @since 0.3.0
 * @author Abe White
 * @author Steve Kim (query metadata)
 */
public class MetaDataRepository implements PCRegistry.RegisterClassListener, Configurable, Closeable, MetaDataModes,
    Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * Constant to not validate any metadata.
     */
    public static final int VALIDATE_NONE = 0;

    /**
     * Bit flag to validate metadata.
     */
    public static final int VALIDATE_META = 1;

    /**
     * Bit flag to validate mappings.
     */
    public static final int VALIDATE_MAPPING = 2;

    /**
     * Bit flag to validate unenhanced metadata only.
     */
    public static final int VALIDATE_UNENHANCED = 4;

    /**
     * Bit flag for runtime validation. Requires that all classes are enhanced, and performs extra
     * field resolution steps.
     */
    public static final int VALIDATE_RUNTIME = 8;

    protected static final Class<?>[] EMPTY_CLASSES = new Class[0];
    protected static final NonPersistentMetaData[] EMPTY_NON_PERSISTENT = new NonPersistentMetaData[0];
    protected final ClassMetaData[] EMPTY_METAS;
    protected final FieldMetaData[] EMPTY_FIELDS;
    protected final Order[] EMPTY_ORDERS;

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

    // system sequence
    private SequenceMetaData _sysSeq = null;
    // cache of parsed metadata, oid class to class, and interface class
    // to metadatas
    private Map<Class<?>, ClassMetaData> _metas = new HashMap<>();
    private Map<String, ClassMetaData> _metaStringMap = new ConcurrentHashMap<>();
    private Map<Class<?>, Class<?>> _oids = Collections.synchronizedMap(new HashMap<>());
    private Map<Class<?>, Collection<Class<?>>> _impls =
        Collections.synchronizedMap(new HashMap<>());
    private Map<Class<?>, Class<?>> _ifaces = Collections.synchronizedMap(new HashMap<>());
    private Map<String, QueryMetaData> _queries = new HashMap<>();
    private Map<String, SequenceMetaData> _seqs = new HashMap<>();
    private Map<String, List<Class<?>>> _aliases = Collections.synchronizedMap(new HashMap<>());
    private Map<Class<?>, NonPersistentMetaData> _pawares =
        Collections.synchronizedMap(new HashMap<>());
    private Map<Class<?>, NonPersistentMetaData> _nonMapped =
        Collections.synchronizedMap(new HashMap<>());
    private Map<Class<?>, Class<?>> _metamodel = Collections.synchronizedMap(new HashMap<>());

    // map of classes to lists of their subclasses
    private Map<Class<?>, Collection<Class<?>>> _subs =
            Collections.synchronizedMap(new HashMap<>());

    // xml mapping
    protected final XMLMetaData[] EMPTY_XMLMETAS;
    private final Map<Class<?>, XMLMetaData> _xmlmetas = new HashMap<>();

    private transient OpenJPAConfiguration _conf = null;
    private transient Log _log = null;
    private transient InterfaceImplGenerator _implGen = null;
    private transient MetaDataFactory _factory = null;

    private int _resMode = MODE_META | MODE_MAPPING;
    private int _sourceMode = MODE_META | MODE_MAPPING | MODE_QUERY;
    private int _validate = VALIDATE_META | VALIDATE_UNENHANCED;

    // we buffer up any classes that register themselves to prevent
    // reentrancy errors if classes register during a current parse (common)
    private final Collection<Class<?>> _registered = new HashSet<>();

    // set of metadatas we're in the process of resolving
    private final List<ClassMetaData> _resolving = new ArrayList<>();
    private final List<ClassMetaData> _mapping = new ArrayList<>();
    private final List<RuntimeException> _errs = new LinkedList<>();

    // system listeners
    private LifecycleEventManager.ListenerList _listeners = new LifecycleEventManager.ListenerList(3);
    private boolean _systemListenersActivated = false;

    protected boolean _preload = false;
    protected boolean _preloadComplete = false;
    protected boolean _locking = true;
    private static final String PRELOAD_STR = "Preload";

    // A boolean used to decide whether or not we need to call to PCEnhancer to check whether we have any down level
    // Entities.
    private boolean _logEnhancementLevel = true;

    // A boolean used to decide whether to filter Class<?> objects submitted by the PCRegistry listener system
    private boolean _filterRegisteredClasses = false;

    // we should skip these types for the enhancement
    private Collection<Class<?>> _typesWithoutEnhancement;

    /**
     * Default constructor. Configure via {@link Configurable}.
     */
    public MetaDataRepository() {
        EMPTY_METAS = newClassMetaDataArray(0);
        EMPTY_FIELDS = newFieldMetaDataArray(0);
        EMPTY_ORDERS = newOrderArray(0);
        EMPTY_XMLMETAS = newXMLClassMetaDataArray(0);

    }

    /**
     * Return the configuration for the repository.
     */
    public OpenJPAConfiguration getConfiguration() {
        return _conf;
    }

    /**
     * Return the metadata log.
     */
    public Log getLog() {
        return _log;
    }

    /**
     * The I/O used to load metadata.
     */
    public MetaDataFactory getMetaDataFactory() {
        return _factory;
    }

    /**
     * The I/O used to load metadata.
     */
    public void setMetaDataFactory(MetaDataFactory factory) {
        factory.setRepository(this);
        _factory = factory;
    }

    /**
     * The metadata validation level. Defaults to <code>VALIDATE_META | VALIDATE_UNENHANCED</code>.
     */
    public int getValidate() {
        return _validate;
    }

    /**
     * The metadata validation level. Defaults to <code>VALIDATE_META | VALIDATE_UNENHANCED</code>.
     */
    public void setValidate(int validate) {
        _validate = validate;
    }

    /**
     * The metadata validation level. Defaults to
     * <code>VALIDATE_META | VALIDATE_MAPPING | VALIDATE_UNENHANCED</code>.
     */
    public void setValidate(int validate, boolean on) {
        if (validate == VALIDATE_NONE)
            _validate = validate;
        else if (on)
            _validate |= validate;
        else
            _validate &= ~validate;
    }

    /**
     * The metadata resolution mode. Defaults to <code>MODE_META | MODE_MAPPING</code>.
     */
    public int getResolve() {
        return _resMode;
    }

    /**
     * The metadata resolution mode. Defaults to <code>MODE_META | MODE_MAPPING</code>.
     */
    public void setResolve(int mode) {
        _resMode = mode;
    }

    /**
     * The metadata resolution mode. Defaults to <code>MODE_META | MODE_MAPPING</code>.
     */
    public void setResolve(int mode, boolean on) {
        if (mode == MODE_NONE)
            _resMode = mode;
        else if (on)
            _resMode |= mode;
        else
            _resMode &= ~mode;
    }

    /**
     * The source mode determining what metadata to load. Defaults to
     * <code>MODE_META | MODE_MAPPING | MODE_QUERY</code>.
     */
    public int getSourceMode() {
        return _sourceMode;
    }

    /**
     * The source mode determining what metadata to load. Defaults to
     * <code>MODE_META | MODE_MAPPING | MODE_QUERY</code>.
     */
    public void setSourceMode(int mode) {
        _sourceMode = mode;
    }

    /**
     * The source mode determining what metadata to load. Defaults to
     * <code>MODE_META | MODE_MAPPING | MODE_QUERY</code>.
     */
    public void setSourceMode(int mode, boolean on) {
        if (mode == MODE_NONE)
            _sourceMode = mode;
        else if (on)
            _sourceMode |= mode;
        else
            _sourceMode &= ~mode;
    }

    /**
     * Sets whether this repository will load all known persistent classes at initialization.
     * Defaults to false.
     */
    public boolean getPreload() {
        return _preload;
    }

    /**
     * Sets whether this repository will load all known persistent classes at initialization.
     * Defaults to false.
     */
    public void setPreload(boolean l) {
        _preload = l;
    }


     /**
     * If the openjpa.MetaDataRepository plugin value Preload=true is set, this method will load all
     * MetaData for all persistent classes and will remove locking from this class.
     */
    public synchronized void preload() {
        if (!_preload) {
            return;
        }
        // If pooling EMFs, this method may be invoked more than once. Only perform this work once.
        if (_preloadComplete) {
            return;
        }


        MultiClassLoader multi = AccessController.doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction());
        multi.addClassLoader(AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction()));
        multi.addClassLoader(AccessController.doPrivileged(J2DoPrivHelper
            .getClassLoaderAction(MetaDataRepository.class)));
        // If a ClassLoader was passed into Persistence.createContainerEntityManagerFactory on the PersistenceUnitInfo
        // we need to add that loader to the chain of classloaders
        ClassResolver resolver = _conf.getClassResolverInstance();
        if (resolver != null) {
            ClassLoader cl = resolver.getClassLoader(null, null);
            if (cl != null) {
                multi.addClassLoader(cl);
            }
        }

        Set<String> classes = getPersistentTypeNames(false, multi);
        if (classes == null || classes.size() == 0) {
            throw new MetaDataException(_loc.get("repos-initializeEager-none"));
        }
        if (_log.isTraceEnabled()) {
            _log.trace(_loc.get("repos-initializeEager-found", classes));
        }

        List<Class<?>> loaded = new ArrayList<>();
        for (String c : classes) {
            try {
                Class<?> cls = AccessController.doPrivileged((J2DoPrivHelper.getForNameAction(c, true, multi)));
                loaded.add(cls);
                // This call may be unnecessary?
                _factory.load(cls, MODE_ALL, multi);
            } catch (PrivilegedActionException pae) {
                throw new MetaDataException(_loc.get("repos-initializeEager-error"), pae);
            }
        }
        resolveAll(multi);

        // Preload XML MetaData
        for (Class<?> cls : loaded) {
            ClassMetaData cmd = getCachedMetaData(cls);
            if (cmd != null) {
                getXMLMetaData(cls);
                for (FieldMetaData fmd : cmd.getFields()) {
                    getXMLMetaData(fmd.getDeclaredType());
                }
            }
        }

        // Hook in this class as a listener and process registered classes list to populate _aliases
        // list.
        PCRegistry.addRegisterClassListener(this);
        processRegisteredClasses(multi);
        _locking = false;
        _preloadComplete = true;
    }


    /**
     * Return the metadata for the given class.
     *
     * @param cls
     *            the class to retrieve metadata for
     * @param envLoader
     *            the environmental class loader, if any
     * @param mustExist
     *            if true, throws a {@link MetaDataException} if no metadata is found
     */
    public ClassMetaData getMetaData(Class<?> cls, ClassLoader envLoader, boolean mustExist) {
        if (_locking) {
            synchronized(this){
                return getMetaDataInternal(cls, envLoader, mustExist);
            }
        } else {
            return getMetaDataInternal(cls, envLoader, mustExist);
        }
    }

    private ClassMetaData getMetaDataInternal(Class<?> cls, ClassLoader envLoader, boolean mustExist) {
        ClassMetaData meta = getMetaDataInternal(cls, envLoader);
        if (meta == null) {
            if (cls != null && DynamicPersistenceCapable.class.isAssignableFrom(cls))
                cls = cls.getSuperclass();

            // if cls is a generated interface, use the user interface
            // to locate metadata
            if (cls != null && _implGen != null && _implGen.isImplType(cls))
                cls = _implGen.toManagedInterface(cls);
            meta = getMetaDataInternal(cls, envLoader);
        }
        if (meta == null && mustExist) {
            if (cls != null && !ImplHelper.isManagedType(_conf, cls))
                throw new MetaDataException(_loc.get("no-meta-notpc", cls)).setFatal(false);

            Set<String> pcNames = getPersistentTypeNames(false, envLoader);
            if (pcNames != null && pcNames.size() > 0)
                throw new MetaDataException(_loc.get("no-meta-types", cls, pcNames));

            throw new MetaDataException(_loc.get("no-meta", cls));
        }
        resolve(meta);
        return meta;
    }

    /**
     * Return the metadata for the given alias name.
     *
     * @param alias
     *            the alias to class to retrieve metadata for
     * @param envLoader
     *            the environmental class loader, if any
     * @param mustExist
     *            if true, throws a {@link MetaDataException} if no metadata is found
     * @see ClassMetaData#getTypeAlias
     */
    public ClassMetaData getMetaData(String alias, ClassLoader envLoader, boolean mustExist) {
        if (alias == null && mustExist)
            throw new MetaDataException(_loc.get("no-alias-meta", alias, _aliases));
        if (alias == null)
            return null;

        // check cache
        processRegisteredClasses(envLoader);
        List<Class<?>> classList = _aliases.get(alias);

        // multiple classes may have been defined with the same alias: we
        // will filter by checking against the current list of the
        // persistent types and filter based on which classes are loadable
        // via the current environment's ClassLoader
        Set<String> pcNames = getPersistentTypeNames(false, envLoader);
        Class<?> cls = null;
        for (int i = 0; classList != null && i < classList.size(); i++) {
            Class<?> c = classList.get(i);
            try {
                // re-load the class in the current environment loader so
                // that we can handle redeployment of the same class name
                Class<?> nc = Class.forName(c.getName(), false, envLoader);

                // if we have specified a list of persistent clases,
                // also check to ensure that the class is in that list
                if (pcNames == null || pcNames.size() == 0 || pcNames.contains(nc.getName())) {
                    cls = nc;
                    if (!classList.contains(cls))
                        classList.add(cls);
                    break;
                }
            } catch (Throwable t) {
                // this happens when the class is not loadable by
                // the environment class loader, so it was probably
                // listed elsewhere; also ignore linkage failures and
                // other class loading problems
            }
        }
        if (cls != null)
            return getMetaData(cls, envLoader, mustExist);

        // maybe this is some type we've seen but just isn't valid
        if (_aliases.containsKey(alias)) {
            if (mustExist)
                throwNoRegisteredAlias(alias);
            return null;
        }

        // We need to synchronize on _aliases because a ConcurrentModificationException can if there
        // is a thread in getAliasNames() AND this class isn't using any locking.
        synchronized (_aliases) {
            // record that this is an invalid type
            _aliases.put(alias, null);
        }

        if (!mustExist)
            return null;
        return throwNoRegisteredAlias(alias);
    }

    private ClassMetaData throwNoRegisteredAlias(String alias) {
        String close = getClosestAliasName(alias);
        if (close != null)
            throw new MetaDataException(_loc.get("no-alias-meta-hint", alias, _aliases, close));
        else
            throw new MetaDataException(_loc.get("no-alias-meta", alias, _aliases));
    }

    /**
     * @return the nearest match to the specified alias name
     * @since 1.1.0
     */
    public String getClosestAliasName(String alias) {
        Collection<String> aliases = getAliasNames();
        return StringDistance.getClosestLevenshteinDistance(alias, aliases);
    }

    /**
     * @return the registered alias names
     * @since 1.1.0
     */
    public Collection<String> getAliasNames() {
        if (_locking) {
            synchronized (_aliases) {
                return getAliasNamesInternal();
            }
        } else {
            return getAliasNamesInternal();
        }
    }

    private Collection<String> getAliasNamesInternal() {
        Collection<String> aliases = new HashSet<>();
        for(Map.Entry<String, List<Class<?>>> e : _aliases.entrySet()){
            if (e.getValue() != null) {
                aliases.add(e.getKey());
            }
        }
        return aliases;
    }

    /**
     * Internal method to get the metadata for the given class, without resolving it.
     */
    private ClassMetaData getMetaDataInternal(Class<?> cls, ClassLoader envLoader) {
        if (cls == null)
            return null;

        // check cache for existing metadata, or give up if no metadata and
        // our list of configured persistent types doesn't include the class
        ClassMetaData meta = _metas.get(cls);
        if (meta != null && ((meta.getSourceMode() & MODE_META) != 0 || (_sourceMode & MODE_META) == 0))
            return meta;

        // if runtime, cut off search if not in pc list. we don't do this at
        // dev time so that user can manipulate persistent classes he's writing
        // before adding them to the list
        if ((_validate & VALIDATE_RUNTIME) != 0) {
            Set<String> pcNames = getPersistentTypeNames(false, envLoader);
            if (pcNames != null && !pcNames.contains(cls.getName()))
                return meta;
        }

        if (meta == null) {
            // check to see if maybe we know this class has no metadata
            if (_metas.containsKey(cls))
                return null;

            // make sure this isn't an obviously bad class
            if (cls.isPrimitive() || cls.getName().startsWith("java.") || cls == PersistenceCapable.class)
                return null;

            // designed to get around jikes 1.17 / JDK1.5 issue where static
            // initializers are not invoked when a class is referenced, so the
            // class never registers itself with the system
            if ((_validate & VALIDATE_RUNTIME) != 0) {
                try {
                    Class.forName(cls.getName(), true, AccessController.doPrivileged(J2DoPrivHelper
                        .getClassLoaderAction(cls)));
                } catch (Throwable t) {
                }
            }
        }

        // not in cache: load metadata or mappings depending on source mode.
        // loading metadata might also load mappings, but doesn't have to
        int mode = 0;
        if ((_sourceMode & MODE_META) != 0)
            mode = _sourceMode & ~MODE_MAPPING;
        else if ((_sourceMode & MODE_MAPPING) == 0)
            mode = _sourceMode;
        if (mode != MODE_NONE) {
            if (_log.isTraceEnabled())
                _log.trace(_loc.get("load-cls", cls, toModeString(mode)));
            _factory.load(cls, mode, envLoader);
        }

        // check cache again
        if (meta == null)
            meta = _metas.get(cls);
        if (meta != null && ((meta.getSourceMode() & MODE_META) != 0 || (_sourceMode & MODE_META) == 0))
            return meta;

        // record that this class has no metadata; checking for this later
        // speeds things up in environments with slow class loading
        // like appservers
        if (meta != null)
            removeMetaData(meta);
        _metas.put(cls, null);
        return null;
    }

    /**
     * Return a string representation of the given mode flags.
     */
    private static String toModeString(int mode) {
        StringBuilder buf = new StringBuilder(31);
        if ((mode & MODE_META) != 0)
            buf.append("[META]");
        if ((mode & MODE_QUERY) != 0)
            buf.append("[QUERY]");
        if ((mode & MODE_MAPPING) != 0)
            buf.append("[MAPPING]");
        if ((mode & MODE_MAPPING_INIT) != 0)
            buf.append("[MAPPING_INIT]");
        return buf.toString();
    }

    /**
     * Prepare metadata for mapping resolution. This method might map parts of the metadata that
     * don't rely on other classes being mapped, but that other classes might rely on during their
     * own mapping (for example, primary key fields). By default, this method only calls
     * {@link ClassMetaData#defineSuperclassFields}.
     */
    protected void prepareMapping(ClassMetaData meta) {
        meta.defineSuperclassFields(false);
    }

    /**
     * Resolve the given metadata if needed. There are three goals:
     * <ol>
     * <li>Make sure no unresolved metadata gets back to the client.</li>
     * <li>Avoid infinite reentrant calls for mutually-dependent metadatas by allowing unresolved
     * metadata to be returned to other metadatas.</li>
     * <li>Always make sure the superclass metadata is resolved before the subclass metadata so that
     * the subclass can access the super's list of fields.</li>
     * </ol>
     * Note that the code calling this method is synchronized, so this method doesn't have to be.
     */
    private void resolve(ClassMetaData meta) {
        // return anything that has its metadata resolved, because that means
        // it is either fully resolved or must at least be in the process of
        // resolving mapping, etc since we do that right after meta resolve
        if (meta == null || _resMode == MODE_NONE || (meta.getResolve() & MODE_META) != 0)
            return;

        // resolve metadata
        List<ClassMetaData> resolved = resolveMeta(meta);
        if (resolved == null)
            return;

        // load mapping data
        for (ClassMetaData data : resolved) {
            loadMapping(data);
        }
        for (ClassMetaData metaData : resolved) {
            preMapping(metaData);
        }

        // resolve mappings
        boolean err = true;
        if ((_resMode & MODE_MAPPING) != 0)
            for (ClassMetaData classMetaData : resolved) {
                err &= resolveMapping(classMetaData);
            }

        // throw errors encountered
        // OPENJPA-1535 Always throw a MetaDataException because callers
        // of loadRegisteredClassMetaData expect only MetaDataException
        // to be thrown.
        if (err && !_errs.isEmpty()) {
            RuntimeException re;
            if ((_errs.size() == 1) && (_errs.get(0) instanceof MetaDataException)) {
                re = _errs.get(0);
            } else {
                re = new MetaDataException(_loc.get("resolve-errs"))
                    .setNestedThrowables(_errs
                    .toArray(new Exception[_errs.size()]));
            }
            _errs.clear();
            throw re;
        }
    }

    /**
     * Resolve metadata mode, returning list of processed metadadatas, or null if we're still in the
     * process of resolving other metadatas.
     */
    private List<ClassMetaData> resolveMeta(ClassMetaData meta) {
        if (meta.getPCSuperclass() == null) {
            // set superclass
            Class<?> sup = meta.getDescribedType().getSuperclass();
            ClassMetaData supMeta;
            while (sup != null && sup != Object.class) {
                supMeta = getMetaData(sup, meta.getEnvClassLoader(), false);
                if (supMeta != null) {
                    meta.setPCSuperclass(sup);
                    meta.setPCSuperclassMetaData(supMeta);
                    break;
                } else
                    sup = sup.getSuperclass();
            }
            if (meta.getDescribedType().isInterface()) {
                Class<?>[] sups = meta.getDescribedType().getInterfaces();
                for (Class<?> aClass : sups) {
                    supMeta = getMetaData(aClass, meta.getEnvClassLoader(), false);
                    if (supMeta != null) {
                        meta.setPCSuperclass(sup);
                        meta.setPCSuperclassMetaData(supMeta);
                        break;
                    }
                }
            }
            if (_log.isTraceEnabled())
                _log.trace(_loc.get("assigned-sup", meta, meta.getPCSuperclass()));
        }

        // resolve relation primary key fields for mapping dependencies
        FieldMetaData[] fmds = meta.getDeclaredFields();
        for (FieldMetaData fmd : fmds)
            if (fmd.isPrimaryKey())
                getMetaData(fmd.getDeclaredType(), meta.getEnvClassLoader(), false);

        // resolve metadata; if we're not in the process of resolving
        // others, this will return the set of interrelated metas that
        // resolved
        return processBuffer(meta, _resolving, MODE_META);
    }

    /**
     * Load mapping information for the given metadata.
     */
    private void loadMapping(ClassMetaData meta) {
        if ((meta.getResolve() & MODE_MAPPING) != 0)
            return;

        // load mapping information
        if ((meta.getSourceMode() & MODE_MAPPING) == 0 && (_sourceMode & MODE_MAPPING) != 0) {
            // embedded-only metadata doesn't have mapping, so always loaded
            if (meta.isEmbeddedOnly())
                meta.setSourceMode(MODE_MAPPING, true);
            else {
                // load mapping data
                int mode = _sourceMode & ~MODE_META;
                if (_log.isTraceEnabled())
                    _log.trace(_loc.get("load-mapping", meta, toModeString(mode)));
                try {
                    _factory.load(meta.getDescribedType(), mode, meta.getEnvClassLoader());
                } catch (RuntimeException re) {
                    removeMetaData(meta);
                    _errs.add(re);
                }
            }
        }
    }

    /**
     * Pre-mapping preparation.
     */
    private void preMapping(ClassMetaData meta) {
        if ((meta.getResolve() & MODE_MAPPING) != 0)
            return;

        // prepare mappings for resolve; if not resolving mappings, then
        // make sure any superclass fields defined in metadata are resolved
        try {
            if ((_resMode & MODE_MAPPING) != 0) {
                if (_log.isTraceEnabled())
                    _log.trace(_loc.get("prep-mapping", meta));
                prepareMapping(meta);
            } else
                meta.defineSuperclassFields(false);
        } catch (RuntimeException re) {
            removeMetaData(meta);
            _errs.add(re);
        }
    }

    /**
     * Resolve and initialize mapping.
     *
     * @return false if we're still in the process of resolving mappings
     */
    private boolean resolveMapping(ClassMetaData meta) {
        List<ClassMetaData> mapped = processBuffer(meta, _mapping, MODE_MAPPING);
        if (mapped == null)
            return false;

        // initialize mapping for runtime use
        if ((_resMode & MODE_MAPPING_INIT) != 0) {
            for (ClassMetaData classMetaData : mapped) {
                meta = classMetaData;
                try {
                    meta.resolve(MODE_MAPPING_INIT);
                }
                catch (RuntimeException re) {
                    removeMetaData(meta);
                    _errs.add(re);
                }
            }
        }
        return true;
    }

    /**
     * Process the given metadata and the associated buffer.
     */
    private List<ClassMetaData> processBuffer(ClassMetaData meta, List<ClassMetaData> buffer, int mode) {
        // add the metadata to the buffer unless an instance for the same entity
        // is already there
        for (ClassMetaData cmd : buffer)
            if (cmd.getDescribedType().equals(meta.getDescribedType()))
                return null;

        // if we're already processing a metadata, just buffer this one; when
        // the initial metadata finishes processing, we traverse the buffer
        // and process all the others that were introduced during reentrant
        // calls
        buffer.add(meta);
        if (buffer.size() != 1)
            return null;

        // continually pop a metadata and process it until we run out; note
        // that each processing call might place more metas in the buffer as
        // one class tries to access metadata for another
        ClassMetaData buffered;
        List<ClassMetaData> processed = new ArrayList<>(5);
        while (!buffer.isEmpty()) {
            buffered = buffer.get(0);
            try {
                buffered.resolve(mode);
                processed.add(buffered);
                buffer.remove(buffered);
            } catch (RuntimeException re) {
                _errs.add(re);

                // any exception during resolution of one type means we can't
                // resolve any of the related types, so clear buffer. this also
                // ensures that if two types relate to each other and one
                // dies, we don't get into infinite cycles
                for (ClassMetaData cmd : buffer) {
                    removeMetaData(cmd);
                    if (cmd != buffered) {
                        _errs.add(new MetaDataException(_loc.get("prev-errs", cmd, buffered)));
                    }
                }
                buffer.clear();
            }
        }

        return processed;
    }

    /**
     * Return all the metadata instances currently in the repository.
     */
    public ClassMetaData[] getMetaDatas() {
        if (_locking) {
            synchronized(this){
                return getMetaDatasInternal();
            }
        } else {
            return getMetaDatasInternal();
        }
    }

    private ClassMetaData[] getMetaDatasInternal() {
            // prevent concurrent mod errors when resolving one metadata
            // introduces others
            ClassMetaData[] metas = _metas.values().toArray(new ClassMetaData[_metas.size()]);
        for (ClassMetaData classMetaData : metas)
