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

import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.Types;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.meta.strats.BlobValueHandler;
import org.apache.openjpa.jdbc.meta.strats.ByteArrayValueHandler;
import org.apache.openjpa.jdbc.meta.strats.CharArrayStreamValueHandler;
import org.apache.openjpa.jdbc.meta.strats.CharArrayValueHandler;
import org.apache.openjpa.jdbc.meta.strats.ClassNameDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.ClobValueHandler;
import org.apache.openjpa.jdbc.meta.strats.ElementEmbedValueHandler;
import org.apache.openjpa.jdbc.meta.strats.EmbedFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.EmbeddedClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.FlatClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.FullClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerCollectionTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerHandlerMapTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerRelationMapTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.ImmutableValueHandler;
import org.apache.openjpa.jdbc.meta.strats.LobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedBlobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedByteArrayFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedCharArrayFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MaxEmbeddedClobFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.MultiColumnVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.
        NanoPrecisionTimestampVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.NoneVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.NumberVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.ObjectIdClassStrategy;
import org.apache.openjpa.jdbc.meta.strats.ObjectIdValueHandler;
import org.apache.openjpa.jdbc.meta.strats.PrimitiveFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.
        RelationCollectionInverseKeyFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationCollectionTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationHandlerMapTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationMapInverseKeyFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.RelationMapTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.
        RelationRelationMapTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.StateComparisonVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.StringFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.SubclassJoinDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.TimestampVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.UntypedPCValueHandler;
import org.apache.openjpa.jdbc.meta.strats.ValueMapDiscriminatorStrategy;
import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.JoinSyntaxes;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.Order;
import org.apache.openjpa.meta.SequenceMetaData;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.util.MetaDataException;
import org.apache.openjpa.util.UserException;

/**
 * Repository of object/relational mapping information.
 *
 * @author Abe White
 */
public class MappingRepository extends MetaDataRepository {

    
    private static final long serialVersionUID = 1L;

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

    private transient DBDictionary _dict = null;
    private transient MappingDefaults _defaults = null;

    // object->queryresultmapping
    private Map<Object, QueryResultMapping> _results = new HashMap<>();
    private SchemaGroup _schema = null;
    private StrategyInstaller _installer = null;

    /**
     * Default constructor.  Configure via
     * {@link org.apache.openjpa.lib.conf.Configurable}.
     */
    public MappingRepository() {
        setValidate(VALIDATE_MAPPING, true);
    }

    /**
     * Convenient access to dictionary for mappings.
     */
    public DBDictionary getDBDictionary() {
        return _dict;
    }

    /**
     * Mapping defaults.
     */
    public MappingDefaults getMappingDefaults() {
        return _defaults;
    }

    /**
     * Mapping default.
     */
    public void setMappingDefaults(MappingDefaults defaults) {
        _defaults = defaults;
    }

    /**
     * Representation of the database schema.
     */
    public SchemaGroup getSchemaGroup() {
        if (_locking) {
            synchronized (this) {
                if (_schema == null)
                    _schema = ((JDBCConfiguration) getConfiguration()).getSchemaFactoryInstance().readSchema();
                return _schema;
            }
        } else {
            if (_schema == null)
                _schema = ((JDBCConfiguration) getConfiguration()).getSchemaFactoryInstance().readSchema();
            return _schema;
        }
    }

    /**
     * Representation of the database schema.
     */
    public void setSchemaGroup(SchemaGroup schema) {
        if (_locking) {
            synchronized (this) {
                _schema = schema;
            }
        } else {
            _schema = schema;
        }
    }

    /**
     * Installs mapping strategies on components.
     */
    public StrategyInstaller getStrategyInstaller() {
        if (_locking) {
            synchronized (this) {
                if (_installer == null)
                    _installer = new RuntimeStrategyInstaller(this);
                return _installer;
            }
        } else {
            if (_installer == null)
                _installer = new RuntimeStrategyInstaller(this);
            return _installer;
        }
    }

    /**
     * Installs mapping strategies on components.
     */
    public void setStrategyInstaller(StrategyInstaller installer) {
        if (_locking) {
            synchronized (this) {
                _installer = installer;
            }
        } else {
            _installer = installer;
        }
    }

    /**
     * Return the query result mapping for the given name.
     */
    public QueryResultMapping getQueryResultMapping(Class<?> cls, String name, ClassLoader loader, boolean mustExist) {
        QueryResultMapping res;
        if (_locking) {
            synchronized (this) {
                res = getQueryResultMappingInternal(cls, name, loader);
            }
        } else {
            res = getQueryResultMappingInternal(cls, name, loader);
        }
        if (res == null && mustExist)
            throw new MetaDataException(_loc.get("no-query-res", cls, name));
        return res;
    }

    /**
     * Returned the query result mapping with the given name.
     */
    private QueryResultMapping getQueryResultMappingInternal(Class<?> cls, String name, ClassLoader envLoader) {
        if (name == null)
            return null;

        // check cache
        Object key = getQueryResultKey(cls, name);
        QueryResultMapping res = _results.get(key);
        if (res != null)
            return res;

        // get metadata for class, which will find results in metadata file
        if (cls != null && getMetaData(cls, envLoader, false) != null) {
            res = _results.get(key);
            if (res != null)
                return res;
        }
        if ((getSourceMode() & MODE_QUERY) == 0)
            return null;

        if (cls == null)
            cls = getMetaDataFactory()
                    .getResultSetMappingScope(name, envLoader);
        // not in cache; load
        getMetaDataFactory().load(cls, MODE_META | MODE_MAPPING, envLoader);
        return _results.get(key);
    }

    /**
     * Return all cached query result mappings.
     */
    public QueryResultMapping[] getQueryResultMappings() {
        if (_locking) {
            synchronized (this) {
                Collection values = _results.values();
                return (QueryResultMapping[]) values.toArray(new QueryResultMapping[values.size()]);
            }
        } else {
            Collection values = _results.values();
            return (QueryResultMapping[]) values.toArray(new QueryResultMapping[values.size()]);
        }
    }

    /**
     * Return the cached query result mapping with the given name, or null if
     * none.
     */
    public QueryResultMapping getCachedQueryResultMapping(Class cls, String name) {
        if (_locking) {
            synchronized (this) {
                return (QueryResultMapping) _results.get(getQueryResultKey(cls, name));
            }
        } else {
            return (QueryResultMapping) _results.get(getQueryResultKey(cls, name));
        }
    }

    /**
     * Add a query result mapping.
     */
    public QueryResultMapping addQueryResultMapping(Class cls, String name) {
        if (_locking) {
            synchronized (this) {
                return addQueryResultMappingInternal(cls, name);
            }
        } else {
            return addQueryResultMappingInternal(cls, name);
        }
    }

    private QueryResultMapping addQueryResultMappingInternal(Class cls, String name) {
        QueryResultMapping res = new QueryResultMapping(name, this);
        res.setDefiningType(cls);
        _results.put(getQueryResultKey(res), res);
        return res;
    }

    /**
     * Remove a query result mapping.
     */
    public boolean removeQueryResultMapping(QueryResultMapping res) {
        if (_locking) {
            synchronized (this) {
                return _results.remove(getQueryResultKey(res)) != null;
            }
        } else {
            return _results.remove(getQueryResultKey(res)) != null;
        }
    }

    /**
     * Remove a query result mapping.
     */
    public boolean removeQueryResultMapping(Class cls, String name) {
        if (_locking) {
            synchronized (this) {
                if (name == null)
                    return false;
                return _results.remove(getQueryResultKey(cls, name)) != null;
            }
        } else {
            if (name == null)
                return false;
            return _results.remove(getQueryResultKey(cls, name)) != null;
        }
    }

    /**
     * Return a unique key for the given mapping.
     */
    private static Object getQueryResultKey(QueryResultMapping res) {
        if (res == null)
            return null;
        return getQueryResultKey(res.getDefiningType(), res.getName());
    }

    /**
     * Return a unique key for the given class / name. The class argument
     * can be null.
     */
    private static Object getQueryResultKey(Class<?> cls, String name) {
        return getQueryKey(cls, name);
    }

    public ClassMapping getMapping(Class<?> cls, ClassLoader envLoader,
        boolean mustExist) {
        return (ClassMapping) super.getMetaData(cls, envLoader, mustExist);
    }

    public ClassMapping[] getMappings() {
        return (ClassMapping[]) super.getMetaDatas();
    }

    public ClassMapping getMapping(Object oid, ClassLoader envLoader,
        boolean mustExist) {
        return (ClassMapping) super.getMetaData(oid, envLoader, mustExist);
    }

    public ClassMapping[] getImplementorMappings(Class<?> cls,
        ClassLoader envLoader, boolean mustExist) {
        return (ClassMapping[]) super.getImplementorMetaDatas(cls, envLoader,
            mustExist);
    }

    @Override
    public void clear() {
        if (_locking) {
            synchronized (this) {
                super.clear();
                _schema = null;
                _results.clear();
            }
        } else {
            super.clear();
            _schema = null;
            _results.clear();
        }
    }

    @Override
    protected void prepareMapping(ClassMetaData meta) {
        // make sure superclass resolved first; resolving superclass may have
        // resolved this mapping
        ClassMapping mapping = (ClassMapping) meta;
        ClassMapping sup = mapping.getPCSuperclassMapping();
        if (sup != null && (mapping.getResolve() & MODE_MAPPING) != 0)
            return;

        // if this mapping is not for a managed interface, ensure that if
        // we have an inheritance hierarchy there is a default strategy
        // applied to the root class
        if (!mapping.getDescribedType().isInterface() &&
            !mapping.isEmbeddedOnly()) {
            // if an inheritance strategy has not been set on this mapping
            // determine if needs one and if so, set it
            if (!hasInheritanceStrategy(mapping)) {
                ClassMapping baseMapping = findBaseClassMapping(mapping);
                if (baseMapping != null)
                    setDefaultInheritanceStrategy(baseMapping);
            }
        }

        // define superclass fields after mapping class, so we can tell whether
        // the class is mapped and needs to redefine abstract superclass fields
        getStrategyInstaller().installStrategy(mapping);
        mapping.defineSuperclassFields(mapping.getJoinablePCSuperclassMapping()
            == null);

        // resolve everything that doesn't involve relations to allow relation
        // mappings to use the others as joinables
        mapping.resolveNonRelationMappings();
    }

    @Override
    protected ClassMetaData newClassMetaData(Class<?> type) {
        return new ClassMapping(type, this);
    }

    @Override
    protected ClassMetaData[] newClassMetaDataArray(int length) {
        return new ClassMapping[length];
    }

    @Override
    protected FieldMetaData newFieldMetaData(String name, Class<?> type,
        ClassMetaData owner) {
        return new FieldMapping(name, type, (ClassMapping) owner);
    }

    @Override
    protected FieldMetaData[] newFieldMetaDataArray(int length) {
        return new FieldMapping[length];
    }

    @Override
    protected ClassMetaData newEmbeddedClassMetaData(ValueMetaData owner) {
        return new ClassMapping(owner);
    }

    @Override
    protected ValueMetaData newValueMetaData(FieldMetaData owner) {
        return new ValueMappingImpl((FieldMapping) owner);
    }

    @Override
    protected SequenceMetaData newSequenceMetaData(String name) {
        return new SequenceMapping(name, this);
    }

    @Override
    protected Order newValueOrder(FieldMetaData owner, boolean asc) {
        return new JDBCValueOrder((FieldMapping) owner, asc);
    }

    @Override
    protected Order newRelatedFieldOrder(FieldMetaData owner,
        FieldMetaData rel, boolean asc) {
        return new JDBCRelatedFieldOrder((FieldMapping) owner,
            (FieldMapping) rel, asc);
    }

    @Override
    protected Order[] newOrderArray(int size) {
        return new JDBCOrder[size];
    }

    /**
     * Create version metadata for the given class.
     */
    protected Version newVersion(ClassMapping cls) {
        return new Version(cls);
    }

    /**
     * Create discriminator metadata for the given class.
     */
    protected Discriminator newDiscriminator(ClassMapping cls) {
        return new Discriminator(cls);
    }

    /**
     * Create raw mapping info for the given instance.
     */
    protected ClassMappingInfo newMappingInfo(ClassMapping cls) {
        ClassMappingInfo info = new ClassMappingInfo();
        info.setClassName(cls.getDescribedType().getName());
        return info;
    }

    /**
     * Create raw mapping info for the given instance.
     */
    protected FieldMappingInfo newMappingInfo(FieldMapping fm) {
        return new FieldMappingInfo();
    }

    /**
     * Create raw mapping info for the given instance.
     */
    protected ValueMappingInfo newMappingInfo(ValueMapping vm) {
        return new ValueMappingInfo();
    }

    /**
     * Create raw mapping info for the given instance.
     */
    protected VersionMappingInfo newMappingInfo(Version version) {
        return new VersionMappingInfo();
    }

    /**
     * Create raw mapping info for the given instance.
     */
    protected DiscriminatorMappingInfo newMappingInfo(Discriminator disc) {
        return new DiscriminatorMappingInfo();
    }

    /**
     * Instantiate the given class' named strategy, or return null if no
     * named strategy.
     */
    protected ClassStrategy namedStrategy(ClassMapping cls) {
        String name = cls.getMappingInfo().getStrategy();
        return instantiateClassStrategy(name, cls);
    }

    /**
     * Return the strategy for the given name.
     */
    protected ClassStrategy instantiateClassStrategy(String name,
        ClassMapping cls) {
        if (name == null)
            return null;
        if (NoneClassStrategy.ALIAS.equals(name))
            return NoneClassStrategy.getInstance();

        String props = Configurations.getProperties(name);
        name = Configurations.getClassName(name);
        Class<?> strat = null;

        // base and vertical strategies use same alias; differentiate on join
        if (FullClassStrategy.ALIAS.equals(name))
            strat = FullClassStrategy.class;
        else if (FlatClassStrategy.ALIAS.equals(name))
            strat = FlatClassStrategy.class;
        else if (VerticalClassStrategy.ALIAS.equals(name))
            strat = VerticalClassStrategy.class;
        try {
            if (strat == null)
                strat = JavaTypes.classForName(name, cls,
                    AccessController.doPrivileged(
                        J2DoPrivHelper.getClassLoaderAction(
                            ClassStrategy.class)), false);
            ClassStrategy strategy =
                (ClassStrategy) AccessController.doPrivileged(
                    J2DoPrivHelper.newInstanceAction(strat));
            Configurations.configureInstance(strategy, getConfiguration(),
                props);
            return strategy;
        } catch (Exception e) {
            if (e instanceof PrivilegedActionException)
                e = ((PrivilegedActionException) e).getException();
            throw new MetaDataException(_loc.get("bad-cls-strategy",
                cls, name), e);
        }
    }

    /**
     * Instantiate the given field's named strategy, or return null if no
     * named strategy.
     */
    protected FieldStrategy namedStrategy(FieldMapping field,
        boolean installHandlers) {
        String name = field.getMappingInfo().getStrategy();
        if (name == null)
            return null;

        if (NoneFieldStrategy.ALIAS.equals(name))
            return NoneFieldStrategy.getInstance();

        String props = Configurations.getProperties(name);
        name = Configurations.getClassName(name);
        try {
            Class<?> c = JavaTypes.classForName(name, field,
                AccessController.doPrivileged(
                    J2DoPrivHelper.getClassLoaderAction(FieldStrategy.class)), false);
            if (FieldStrategy.class.isAssignableFrom(c)) {
                FieldStrategy strat = (FieldStrategy)
                    AccessController.doPrivileged(
                        J2DoPrivHelper.newInstanceAction(c));
                Configurations.configureInstance(strat, getConfiguration(),
                    props);
                return strat;
            }

            // must be named handler
            if (installHandlers) {
                ValueHandler vh = (ValueHandler) AccessController.doPrivileged(
                    J2DoPrivHelper.newInstanceAction(c));
                Configurations.configureInstance(vh, getConfiguration(),
                    props);
                field.setHandler(vh);
            }
            return new HandlerFieldStrategy();
        } catch (Exception e) {
            if (e instanceof PrivilegedActionException)
                e = ((PrivilegedActionException) e).getException();
            throw new MetaDataException(_loc.get("bad-field-strategy",
                field, name), e);
        }
    }

    /**
     * Instantiate the given discriminator's named strategy, or return null
     * if no named strategy.
     */
    protected DiscriminatorStrategy namedStrategy(Discriminator discrim) {
        String name = discrim.getMappingInfo().getStrategy();
        if (name == null)
            return null;

        // if there is a named strategy present, discard it if it matches
        // the base strategy, so that we won't create an independent instance
        ClassMapping cls = discrim.getClassMapping();
        while (cls.getJoinablePCSuperclassMapping() != null)
            cls = cls.getJoinablePCSuperclassMapping();
        Discriminator base = cls.getDiscriminator();
        if (base != discrim && base.getStrategy() != null
            && name.equals(base.getStrategy().getAlias()))
            return null;

        return instantiateDiscriminatorStrategy(name, discrim);
    }

    /**
     * Instantiate the given discriminator strategy.
     */
    protected DiscriminatorStrategy instantiateDiscriminatorStrategy
        (String name, Discriminator discrim) {
        if (NoneDiscriminatorStrategy.ALIAS.equals(name))
            return NoneDiscriminatorStrategy.getInstance();

        String props = Configurations.getProperties(name);
        name = Configurations.getClassName(name);
        Class<?> strat = null;

        if (ClassNameDiscriminatorStrategy.ALIAS.equals(name))
            strat = ClassNameDiscriminatorStrategy.class;
        else if (ValueMapDiscriminatorStrategy.ALIAS.equals(name))
            strat = ValueMapDiscriminatorStrategy.class;
        else if (SubclassJoinDiscriminatorStrategy.ALIAS.equals(name))
            strat = SubclassJoinDiscriminatorStrategy.class;

        try {
            if (strat == null)
                strat = JavaTypes.classForName(name,
                    discrim.getClassMapping(),
                    AccessController.doPrivileged(
                        J2DoPrivHelper.getClassLoaderAction(
                            DiscriminatorStrategy.class)), false);
            DiscriminatorStrategy strategy = (DiscriminatorStrategy)
                AccessController.doPrivileged(
                    J2DoPrivHelper.newInstanceAction(strat));
            Configurations.configureInstance(strategy, getConfiguration(),
                props);
            return strategy;
        } catch (Exception e) {
            if (e instanceof PrivilegedActionException)
                e = ((PrivilegedActionException) e).getException();
            throw new MetaDataException(_loc.get("bad-discrim-strategy",
                discrim.getClassMapping(), name), e);
        }
    }

    /**
     * Instantiate the given version's named strategy, or return null
     * if no named strategy.
     */
    protected VersionStrategy namedStrategy(Version version) {
        String name = version.getMappingInfo().getStrategy();
        if (name == null)
            return null;

        // if there is a named strategy present, discard it if it matches
        // the base strategy, so that we won't create an independent instance
        ClassMapping cls = version.getClassMapping();
        while (cls.getJoinablePCSuperclassMapping() != null)
            cls = cls.getJoinablePCSuperclassMapping();
        Version base = cls.getVersion();
        if (base != version && base.getStrategy() != null
            && name.equals(base.getStrategy().getAlias()))
            return null;

        return instantiateVersionStrategy(name, version);
    }

    /**
     * Instantiate the given version strategy.
     */
    protected VersionStrategy instantiateVersionStrategy(String name,
        Version version) {
        if (NoneVersionStrategy.ALIAS.equals(name))
            return NoneVersionStrategy.getInstance();

        String props = Configurations.getProperties(name);
        name = Configurations.getClassName(name);
        Class<?> strat = null;

        if (NumberVersionStrategy.ALIAS.equals(name))
            strat = NumberVersionStrategy.class;
        else if (MultiColumnVersionStrategy.ALIAS.equals(name))
        	strat = MultiColumnVersionStrategy.class;
        else if (TimestampVersionStrategy.ALIAS.equals(name))
            strat = TimestampVersionStrategy.class;
        else if (NanoPrecisionTimestampVersionStrategy.ALIAS.equals(name))
            strat = NanoPrecisionTimestampVersionStrategy.class;
        else if (StateComparisonVersionStrategy.ALIAS.equals(name))
            strat = StateComparisonVersionStrategy.class;

        try {
            if (strat == null)
                strat = JavaTypes.classForName(name,
                    version.getClassMapping(),
                    AccessController.doPrivileged(
                        J2DoPrivHelper.getClassLoaderAction(
                            VersionStrategy.class)), false);
        } catch (Exception e) {
            throw new MetaDataException(_loc.get("bad-version-strategy",
                version.getClassMapping(), name), e);
        }

        return instantiateVersionStrategy(strat, version, props);
    }

    /**
     * Instantiate the given version strategy.
     */
    protected VersionStrategy instantiateVersionStrategy(Class<?> strat,
        Version version, String props) {
        try {
            VersionStrategy strategy = (VersionStrategy)
                AccessController.doPrivileged(
                    J2DoPrivHelper.newInstanceAction(strat));
            Configurations.configureInstance(strategy, getConfiguration(),
                props);
            return strategy;
        } catch (Exception e) {
            if (e instanceof PrivilegedActionException)
                e = ((PrivilegedActionException) e).getException();
            throw new MetaDataException(_loc.get("bad-version-strategy",
                version.getClassMapping(), strat + ""), e);
        }
    }

    /**
     * Determine the default strategy to use for the given class. Does
     * not take into account the current strategy, if any.
     */
    protected ClassStrategy defaultStrategy(ClassMapping cls) {
        return defaultStrategy(cls, getStrategyInstaller().isAdapting());
    }

    /**
     * Determine the default strategy to use for the given class. Does
     * not take into account the current strategy, if any.
     */
    protected ClassStrategy defaultStrategy(ClassMapping cls,
        boolean adapting) {
        ValueMapping embed = cls.getEmbeddingMapping();
        if (embed != null) {
            // superclass of embedded class isn't mapped
            if (embed.getType() != cls.getDescribedType()
                || embed.getFieldMapping().getStrategy()
                == NoneFieldStrategy.getInstance())
                return NoneClassStrategy.getInstance();
            if (embed.getTypeCode() == JavaTypes.OID)
                return new ObjectIdClassStrategy();
            return new EmbeddedClassStrategy();
        }
        if (cls.isEmbeddedOnly())
            return NoneClassStrategy.getInstance();

        Object strat = _defaults.getStrategy(cls, adapting);
        if (strat instanceof String)
            return instantiateClassStrategy((String) strat, cls);
        if (strat != null)
            return (ClassStrategy) strat;

        // see if there is a declared hierarchy strategy
        ClassStrategy hstrat = null;
        for (ClassMapping base = cls; base != null && hstrat == null;) {
            hstrat = instantiateClassStrategy(base.getMappingInfo().
                getHierarchyStrategy(), cls);
            base = base.getMappedPCSuperclassMapping();
        }

        // the full strategy as applied to a hierarchy is a
        // table-per-concrete-class strategy, so don't map abstract types
        if (hstrat instanceof FullClassStrategy
            && !cls.isManagedInterface()
            && Modifier.isAbstract(cls.getDescribedType().getModifiers()))
            return NoneClassStrategy.getInstance();

        ClassMapping sup = cls.getMappedPCSuperclassMapping();
        if (sup == null)
            return new FullClassStrategy();
        if (hstrat != null)
            return hstrat;
        return new FlatClassStrategy();
    }

    /**
     * Determine the default strategy to use for the given field. Does
     * not take into account the named or current strategy, if any. If a
     * non-null strategy is returned, this method may as a side effect install
     * value handlers on the field's value mappings.
     */
    protected FieldStrategy defaultStrategy(FieldMapping field,
        boolean installHandlers) {
        return defaultStrategy(field, installHandlers,
            getStrategyInstaller().isAdapting());
    }

    /**
     * Determine the default strategy to use for the given field. Does
     * not take into account the named or current strategy, if any. If a
     * non-null strategy is returned, this method may as a side effect install
     * value handlers on the field's value mappings.
     */
    protected FieldStrategy defaultStrategy(FieldMapping field,
        boolean installHandlers, boolean adapting) {
        // not persistent?
        if (field.getManagement() != FieldMetaData.MANAGE_PERSISTENT
            || field.isVersion())
            return NoneFieldStrategy.getInstance();
        if (field.getDefiningMapping().getStrategy() ==
            NoneClassStrategy.getInstance())
            return NoneFieldStrategy.getInstance();

        // check for named handler first
        ValueHandler handler = namedHandler(field);
        if (handler != null) {
            if (installHandlers)
                field.setHandler(handler);
            return new HandlerFieldStrategy();
        }

        // check for an explicitly mapped strategy
        Object explicitStrat = mappedStrategy(field, field.getType(), adapting);
        if (explicitStrat != null) {
            if (explicitStrat instanceof FieldStrategy)
                return (FieldStrategy) explicitStrat;
            if (explicitStrat != null) {
                if (installHandlers)
                    field.setHandler((ValueHandler) explicitStrat);
                return new HandlerFieldStrategy();
            }
        }

        if (field.isSerialized()) {
            if (_dict.maxEmbeddedBlobSize != -1) {
                handler = defaultHandler(field, adapting);
                if (handler != null) {
                    if (installHandlers)
                        field.setHandler(handler);
                }
                return new MaxEmbeddedBlobFieldStrategy();
            }
        }

        // check for known field strategies
