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

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.TreeSet;

import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.CollectionAttribute;
import jakarta.persistence.metamodel.ListAttribute;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.PluralAttribute.CollectionType;
import jakarta.persistence.metamodel.SetAttribute;
import jakarta.persistence.metamodel.SingularAttribute;

import org.apache.openjpa.conf.Compatibility;
import org.apache.openjpa.kernel.Filters;
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;

/**
 * Implements the managed persistent type and its attributes.
 *
 * Provides identity and version attribute facilities for Identifiable type but does not
 * implement it.
 *
 * @author Pinaki Poddar
 *
 * @param <X> the
 */
public abstract class AbstractManagedType<X> extends Types.BaseType<X>
    implements ManagedType<X> {

    private static final Localizer _loc = Localizer.forPackage(AbstractManagedType.class);
    public final MetamodelImpl model;
    public final ClassMetaData meta;

    private java.util.Set<Attribute<? super X, ?>> attrs = new HashSet<>();

    private final DeclaredAttributeFilter<X> declaredAttributeFilter;
    private final SingularAttributeFilter<X> singularAttributeFilter;
    private final SingularAttributeFilter<X> pluralAttributeFilter;

    /**
     * A protected constructor for creating psudo-managed types.
     */
    protected AbstractManagedType(Class<X> cls, MetamodelImpl model) {
        super(cls);
        this.model = model;
        this.meta = null;
        declaredAttributeFilter = null;
        singularAttributeFilter = null;
        pluralAttributeFilter   = null;
    }

    /**
     * Construct a managed type. The supplied metadata must be resolved i.e. all
     * its fields populated. Because this receiver will populate its attributes
     * corresponding to the available fields of the metadata.
     *
     */
    public AbstractManagedType(ClassMetaData meta, MetamodelImpl model) {
        super((Class<X>) meta.getDescribedType());

        this.model = model;
        this.meta = meta;
        FieldMetaData[] fmds = meta.getFields();
        for (FieldMetaData f : fmds) {
            int decCode = f.getDeclaredTypeCode();
            switch (decCode) {
            case JavaTypes.BOOLEAN:
            case JavaTypes.BOOLEAN_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Boolean>(this, f));
                break;
            case JavaTypes.BYTE:
            case JavaTypes.BYTE_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Byte>(this, f));
                break;
            case JavaTypes.CHAR:
            case JavaTypes.CHAR_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Character>(this, f));
                break;
            case JavaTypes.DOUBLE:
            case JavaTypes.DOUBLE_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Double>(this, f));
                break;
            case JavaTypes.FLOAT:
            case JavaTypes.FLOAT_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Float>(this, f));
                break;
            case JavaTypes.INT:
            case JavaTypes.INT_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Integer>(this, f));
                break;
            case JavaTypes.LONG:
            case JavaTypes.LONG_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Long>(this, f));
                break;
            case JavaTypes.SHORT:
            case JavaTypes.SHORT_OBJ:
                attrs.add(new Members.SingularAttributeImpl<X, Short>(this, f));
                break;
            case JavaTypes.STRING:
                attrs.add(new Members.SingularAttributeImpl<X, String>(this, f));
                break;
            case JavaTypes.NUMBER:
                attrs.add(new Members.SingularAttributeImpl<X, Number>(this, f));
                break;
            case JavaTypes.DATE:
                attrs.add(new Members.SingularAttributeImpl<X, Date>(this, f));
                break;
            case JavaTypes.LOCAL_DATE:
                attrs.add(new Members.SingularAttributeImpl<X, LocalDate>(this, f));
                break;
            case JavaTypes.LOCAL_TIME:
                attrs.add(new Members.SingularAttributeImpl<X, LocalTime>(this, f));
                break;
            case JavaTypes.LOCAL_DATETIME:
                attrs.add(new Members.SingularAttributeImpl<X, LocalDateTime>(this, f));
                break;
            case JavaTypes.OFFSET_TIME:
                attrs.add(new Members.SingularAttributeImpl<X, OffsetTime>(this, f));
                break;
            case JavaTypes.OFFSET_DATETIME:
                attrs.add(new Members.SingularAttributeImpl<X, OffsetDateTime>(this, f));
                break;
            case JavaTypes.CALENDAR:
                attrs.add(new Members.SingularAttributeImpl<X, Calendar>(this, f));
                break;
            case JavaTypes.BIGDECIMAL:
                attrs.add(new Members.SingularAttributeImpl<X, BigDecimal>(this, f));
                break;
            case JavaTypes.BIGINTEGER:
                attrs.add(new Members.SingularAttributeImpl<X, BigInteger>(this, f));
                break;
            case JavaTypes.LOCALE:
                attrs.add(new Members.SingularAttributeImpl<X, Locale>(this, f));
                break;
            case JavaTypes.OBJECT:
            case JavaTypes.OID:
            case JavaTypes.PC:
            case JavaTypes.PC_UNTYPED:
            case JavaTypes.INPUT_READER:
            case JavaTypes.INPUT_STREAM:
                attrs.add(new Members.SingularAttributeImpl(this, f));
                break;
            case JavaTypes.ARRAY:
                Compatibility compat = meta.getRepository().getConfiguration().getCompatibilityInstance();
                if(compat.getUseListAttributeForArrays() || f.isPersistentCollection()) {
                    attrs.add(new Members.ListAttributeImpl(this, f));
                }
                else {
                    attrs.add(new Members.SingularAttributeImpl(this, f));
                }
                break;
            case JavaTypes.COLLECTION:
                switch (MetamodelImpl.categorizeCollection(f.getDeclaredType())) {
                case COLLECTION:
                    attrs.add(new Members.CollectionAttributeImpl(this, f));
                    break;
                case LIST:
                    attrs.add(new Members.ListAttributeImpl(this, f));
                    break;
                case SET:
                    attrs.add(new Members.SetAttributeImpl(this, f));
                    break;
                }
                break;
            case JavaTypes.MAP:
                attrs.add(new Members.MapAttributeImpl(this, f));
                break;
            case JavaTypes.ENUM:
                attrs.add(new Members.SingularAttributeImpl(this, f));
                break;
            default:
                throw new IllegalStateException(_loc.get("field-unrecognized",
                        f.getFullName(false), decCode).getMessage());
            }
        }
        declaredAttributeFilter = new DeclaredAttributeFilter<>(this);
        singularAttributeFilter = new SingularAttributeFilter<>();
        pluralAttributeFilter = new SingularAttributeFilter<X>().inverse();
    }

    /**
     * Returns all the attributes of the managed type including attributes of the super type.
     *
     */
    @Override
    public java.util.Set<Attribute<? super X, ?>> getAttributes() {
        return Collections.unmodifiableSet(attrs);
    }

    /**
     * Returns all the attributes declared by this managed type only.
     *
     */
    @Override
    public java.util.Set<Attribute<X, ?>> getDeclaredAttributes() {
        return filter(attrs, new TreeSet<>(),
                declaredAttributeFilter);
    }

    /**
     * Returns the single-valued attributes of the managed type.
     *
     */
    @Override
    public java.util.Set<SingularAttribute<? super X, ?>> getSingularAttributes() {
        return filter(attrs, new TreeSet<>(),
                singularAttributeFilter);
    }

    /**
     * Returns the single-valued attributes declared by the managed type.
     *
     */
    @Override
    public java.util.Set<SingularAttribute<X, ?>> getDeclaredSingularAttributes() {
        return filter(attrs, new TreeSet<>(),
                declaredAttributeFilter,
                singularAttributeFilter);
    }

    /**
     * Returns the attribute of the given name and Java type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    public <Y> Attribute<? super X, Y> getAttribute(String name, Class<Y> type) {
        Attribute<? super X, ?> result = pick(attrs,
                new AttributeNameFilter<>(name),
                new AttributeTypeFilter<>(type));
        if (result == null)
            notFoundException("attr-not-found", name, type);

        return (Attribute<? super X, Y>)result;
    }

    /**
     * Returns the single-valued attribute of the given name and Java type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <Y> SingularAttribute<? super X, Y> getSingularAttribute(String name, Class<Y> type) {
        Attribute<? super X, ?> result = pick(attrs,
                new AttributeNameFilter<>(name),
                new AttributeTypeFilter<>(type),
                singularAttributeFilter);
        if (result == null)
            notFoundException("attr-not-found-single", name, type);

        return (SingularAttribute<? super X, Y>) result;
    }

    /**
     * Returns the declared attribute of the given name and Java type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    public <Y> Attribute<X, Y> getDeclaredAttribute(String name, Class<Y> type) {
        Attribute<? super X, ?> result = pick(attrs,
                new AttributeNameFilter<>(name),
                new AttributeTypeFilter<>(type),
                declaredAttributeFilter);
        if (result == null)
            notFoundException("attr-not-found-decl-single",name, type);

        return (Attribute<X, Y>) result;
    }

    /**
     * Returns the declared single-valued attribute of the given name and Java type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <Y> SingularAttribute<X, Y> getDeclaredSingularAttribute(String name, Class<Y> type) {
        Attribute<? super X, ?> result = pick(attrs,
                new AttributeNameFilter<>(name),
                new AttributeTypeFilter<>(type),
                declaredAttributeFilter,
                singularAttributeFilter);
        if (result == null)
            notFoundException("attr-not-found-decl-single",name, type);

        return (SingularAttribute<X, Y>) result;
    }

    /**
     * Returns all collection-valued attributes of the managed type.
     *
     */
    @Override
    public java.util.Set<PluralAttribute<? super X, ?, ?>> getPluralAttributes() {
        return filter(attrs, new HashSet<>(),
                pluralAttributeFilter);
    }

    /**
     * Return all collection-valued attributes declared by the managed type.
     *
     */
    @Override
    public java.util.Set<PluralAttribute<X, ?, ?>> getDeclaredPluralAttributes() {
        return filter(attrs, new HashSet<>(),
                declaredAttributeFilter,
                pluralAttributeFilter);
    }

    /**
     * Returns the attribute of the given name, of type java.util.Collection and contains the
     * given element type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <E> CollectionAttribute<? super X, E> getCollection(String name, Class<E> elementType) {
        Attribute<? super X, ?> result = pick(attrs,
                new PluralCategoryFilter<>(CollectionType.COLLECTION),
                new ElementTypeFilter<>(elementType),
                new AttributeNameFilter<>(name));
        if (result == null)
            notFoundException("attr-not-found-coll", name, elementType);

        return (CollectionAttribute<? super X, E>) result;
    }

    /**
     * Returns the attribute of the given name, of type java.util.Set and contains the
     * given element type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <E> SetAttribute<? super X, E> getSet(String name, Class<E> elementType) {
        Attribute<? super X, ?> result = pick(attrs,
                new PluralCategoryFilter<>(CollectionType.SET),
                new ElementTypeFilter<>(elementType),
                new AttributeNameFilter<>(name));
        if (result == null)
            notFoundException("attr-not-found-set",name, elementType);

        return (SetAttribute<? super X, E>) result;
    }

    /**
     * Returns the attribute of the given name, of type java.util.List and contains the
     * given element type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <E> ListAttribute<? super X, E> getList(String name, Class<E> elementType) {
        Attribute<? super X, ?> result = pick(attrs,
                new PluralCategoryFilter<>(CollectionType.LIST),
                new ElementTypeFilter<>(elementType),
                new AttributeNameFilter<>(name));
        if (result == null)
            notFoundException("attr-not-found-list",name, elementType);

        return (ListAttribute<? super X, E>) result;
    }

    /**
     * Returns the attribute of the given name, of type java.util.Map and contains the
     * given key/value type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <K, V> MapAttribute<? super X, K, V> getMap(String name, Class<K> keyType,
        Class<V> valueType) {
        Attribute<? super X, ?> result = pick(attrs,
                new AttributeNameFilter<>(name),
                new PluralCategoryFilter<>(CollectionType.MAP),
                new EntryTypeFilter<>(keyType, valueType));
        if (result == null)
            notFoundException("attr-not-found-map", name, keyType, valueType);

        return (MapAttribute<? super X, K, V>) result;
    }

    /**
     * Returns the declared attribute of the given name, of type java.util.Collection and contains
     * the given element type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <E> CollectionAttribute<X, E> getDeclaredCollection(String name,  Class<E> elementType) {
        Attribute<? super X, ?> result = pick(attrs,
                declaredAttributeFilter,
                new PluralCategoryFilter<>(CollectionType.COLLECTION),
                new ElementTypeFilter<>(elementType),
                new AttributeNameFilter<>(name));
        if (result == null)
            notFoundException("attr-not-found-decl-coll", name, elementType);

        return (CollectionAttribute<X, E>) result;
    }

    /**
     * Returns the declared attribute of the given name, of type java.util.Set and contains
     * the given element type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <E> SetAttribute<X, E> getDeclaredSet(String name, Class<E> elementType) {
        Attribute<? super X, ?> result = pick(attrs,
                declaredAttributeFilter,
                new PluralCategoryFilter<>(CollectionType.SET),
                new AttributeNameFilter<>(name));
        if (result == null)
            notFoundException("attr-not-found-decl-set", name, elementType);

        return (SetAttribute<X, E>) result;
    }

    /**
     * Returns the declared attribute of the given name, of type java.util.List and contains
     * the given element type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <E> ListAttribute<X, E> getDeclaredList(String name, Class<E> elementType) {
        Attribute<? super X, ?> result = pick(attrs,
                declaredAttributeFilter,
                new PluralCategoryFilter<>(CollectionType.LIST),
                new ElementTypeFilter<>(elementType),
                new AttributeNameFilter<>(name));
        if (result == null)
            notFoundException("attr-not-found-decl-list", name, elementType);

        return (ListAttribute<X, E>) result;
    }

    /**
     * Returns the declared attribute of the given name, of type java.util.Map and contains
     * the given key/value type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public <K, V> MapAttribute<X, K, V> getDeclaredMap(String name, Class<K> keyType,
        Class<V> valueType) {
        Attribute<? super X, ?> result = pick(attrs,
                declaredAttributeFilter,
                new AttributeNameFilter<>(name),
                new PluralCategoryFilter<>(CollectionType.MAP),
                new EntryTypeFilter<>(keyType, valueType));
        if (result == null)
            notFoundException("attr-not-found-decl-map", name, keyType, valueType);

        return (MapAttribute<X, K, V>) result;
    }

    // ==============================================================================
    // No type checking
    // ==============================================================================

    /**
     * Returns the attribute of the given name of any type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public Attribute<? super X, ?> getAttribute(String name) {
        return getAttribute(name, null);
    }

    /**
     * Returns the declared attribute of the given name of any type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public Attribute<X, ?> getDeclaredAttribute(String name) {
        return getDeclaredAttribute(name, null);
    }

    /**
     * Returns the single-valued attribute of the given name of any type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public SingularAttribute<? super X, ?> getSingularAttribute(String name) {
        return getSingularAttribute(name, null);
    }

    /**
     * Returns the declared, single-valued attribute of the given name of any type.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public SingularAttribute<X, ?> getDeclaredSingularAttribute(String name) {
        return getDeclaredSingularAttribute(name, null);
    }

    /**
     * Returns the attribute of the given name and of type java.util.Collection.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public CollectionAttribute<? super X, ?> getCollection(String name) {
        return getCollection(name, null);
    }

    /**
     * Returns the attribute of the given name and of type java.util.Set.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public SetAttribute<? super X, ?> getSet(String name) {
        return getSet(name, null);
    }

    /**
     * Returns the attribute of the given name and of type java.util.List.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public ListAttribute<? super X, ?> getList(String name) {
        return getList(name, null);
    }

    /**
     * Returns the attribute of the given name and of type java.util.Map.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public MapAttribute<? super X, ?, ?> getMap(String name) {
        return getMap(name, null, null);
    }

    /**
     * Returns the declared attribute of the given name and of type java.util.Collection.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public CollectionAttribute<X, ?> getDeclaredCollection(String name) {
        return getDeclaredCollection(name, null);
    }

    /**
     * Returns the declared attribute of the given name and of type java.util.Set.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public SetAttribute<X, ?> getDeclaredSet(String name) {
        return getDeclaredSet(name, null);
    }

    /**
     * Returns the declared attribute of the given name and of type java.util.List.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public ListAttribute<X, ?> getDeclaredList(String name) {
        return getDeclaredList(name, null);
    }

    /**
     * Returns the declared attribute of the given name and of type java.util.Map.
     *
     * @throws IllegalArgumentException  if no such attribute exists
     */
    @Override
    public MapAttribute<X, ?, ?> getDeclaredMap(String name) {
        return getDeclaredMap(name, null, null);
    }

    //--------------------------------------------------------------------------
    // Primary Key and Version Attribute related functions
    //--------------------------------------------------------------------------
    /**
     *   Returns the attributes corresponding to the id class of the identifiable type.
     *
     *   @throws IllegalArgumentException if the this type is not using an id class
     */
    public final java.util.Set<SingularAttribute<? super X, ?>> getIdClassAttributes() {
        if (meta.isOpenJPAIdentity())
            throw new IllegalArgumentException(meta + " does not use IdClass. Object Id type = " +
                meta.getObjectIdType() + " Identity Type = " + meta.getIdentityType());
        return filter(attrs, new HashSet<>(),
                new IdAttributeFilter<>());
    }

    /**
     *  Returns the attribute of given type that corresponds to the id attribute of this
     *  identifiable managed type.
     *
     *  @throws IllegalArgumentException if no such attribute exists
     */
     public final <Y> SingularAttribute<? super X, Y> getId(Class<Y> type) {
         Attribute<? super X, ?> result =  pick(attrs,
                 new AttributeTypeFilter<>(type),
                 new IdAttributeFilter<>());
         if (result != null)
             return (SingularAttribute<? super X, Y>) result;
         throw new IllegalArgumentException();
     }

     /**
      *  Returns the declared attribute of given type that corresponds to the id attribute of this
      *  identifiable managed type.
      *
      *  @throws IllegalArgumentException if no such attribute exists
      */
     public final <Y> SingularAttribute<X, Y> getDeclaredId(Class<Y> type) {
         Attribute<? super X, ?> result =  pick(attrs,
                 declaredAttributeFilter,
                 new AttributeTypeFilter<>(type),
                 new IdAttributeFilter<>());
         if (result != null)
             return (SingularAttribute<X, Y>) result;
         throw new IllegalArgumentException();
     }

     /**
      *  Returns the attribute of given type that corresponds to the version attribute of this
      *  managed type.
      *
      *  @throws IllegalArgumentException if no such attribute exists
      */
     public <Y> SingularAttribute<? super X, Y> getVersion(Class<Y> type) {
         Attribute<? super X, ?> result = pick(attrs,
                 new VersionAttributeFilter<>(),
                 new AttributeTypeFilter<>(type));
         if (result == null)
             notFoundException("version-not-found", "", type);
         return (SingularAttribute<? super X, Y>)result;
     }

     /**
      *  Returns the declared attribute of given type that corresponds to the version attribute of
      *  this managed type.
      *
      *  @throws IllegalArgumentException if no such attribute exists
      */
     public <Y> SingularAttribute<X, Y> getDeclaredVersion(Class<Y> type) {
         Attribute<? super X, ?> result = pick(attrs,
                 declaredAttributeFilter,
                 new VersionAttributeFilter<>(),
                 new AttributeTypeFilter<>(type));
         if (result == null)
             notFoundException("decl-version-not-found", "", type);
         return (SingularAttribute<X, Y>)result;
     }

    // =====================================================================
    // Support functions
    // =====================================================================

    FieldMetaData getField(String name) {
        return getField(name, null, null, null, false);
    }

    FieldMetaData getField(String name, Class type) {
        return getField(name, type, null, null, false);
    }

    FieldMetaData getField(String name, Class type, boolean declaredOnly) {
        return getField(name, type, null, null, declaredOnly);
    }

    /**
     * Get the field of the given name after validating the conditions. null
     * value on any condition implies not to validate.
     *
     * @param name simple name i.e. without the class name
     * @param type the expected type of the field.
     * @param element
     *            the expected element type of the field.
     * @param key
     *            the expected key type of the field.
     * @param declared
     *            is this field declared in this receiver
     *
     * @exception IllegalArgumentException
     *                if any of the validation fails.
     *
     */
    FieldMetaData getField(String name, Class<?> type, Class<?> elementType,
            Class<?> keyType, boolean decl) {
        FieldMetaData fmd = decl ? meta.getDeclaredField(name) : meta
                .getField(name);

        if (fmd == null) {
            if (decl && meta.getField(name) != null) {
                throw new IllegalArgumentException(_loc.get("field-not-decl",
                        name, cls, meta.getField(name).getDeclaringType())
                        .getMessage());
            } else {
                throw new IllegalArgumentException(_loc.get("field-missing",
                        name, meta.getDescribedType(),
                        Arrays.toString(meta.getFieldNames())).getMessage());
            }
        }
        assertType("field-type-mismatch", fmd, fmd.getDeclaredType(), type);
        assertType("field-element-type-mismatch", fmd, fmd.getElement()
                .getDeclaredType(), elementType);
        assertType("field-key-type-mismatch", fmd, fmd.getKey()
                .getDeclaredType(), keyType);
        return fmd;
    }

    void assertType(String msg, FieldMetaData fmd, Class<?> actual,
            Class<?> expected) {
        if (expected != null && !expected.isAssignableFrom(actual)) {
            if (wrap(expected) != wrap(actual)) {
                throw new IllegalArgumentException(_loc.get(msg, fmd.getName(),
                        actual, expected).getMessage());
            }
        }
    }

    Class<?> wrap(Class<?> c) {
        if (c.isPrimitive()) {
            if (c == int.class)
                return Integer.class;
            if (c == long.class)
                return Long.class;
            if (c == boolean.class)
                return Boolean.class;
            if (c == byte.class)
                return Byte.class;
            if (c == char.class)
                return Character.class;
            if (c == double.class)
                return Double.class;
            if (c == float.class)
                return Float.class;
            if (c == short.class)
                return Short.class;
        }
        return c;
    }

    // -------------------------------------------------------------------------
    // Exception handling
    // -------------------------------------------------------------------------
    private void notFoundException(String msg, String name, Class<?> t1) {
        throw new IllegalArgumentException(
            _loc.get(msg, name, (t1 == null ? "any" : t1.getName()), meta).getMessage());
    }

    private void notFoundException(String msg, String name, Class<?> t1, Class<?> t2) {
        throw new IllegalArgumentException(
            _loc.get(msg, new Object[]{name, (t1 == null ? "any" : t1.getName()),
                    (t2 == null ? "any" : t1.getName()), meta}).getMessage());
    }
    // --------------------------------------------------------------------------
    // Attribute filtering
    // --------------------------------------------------------------------------
    /**
     * Affirms if a given element satisfy a condition.
     *
     */
    public interface Filter<T> {
        boolean selects(T attr);

        Filter<T> inverse();
    }


    /**
     * Applies chain of filters ANDed on the given collection to populate the given result.
     * A null filter evaluates always TRUE.
     * The arguments are not passed as variable argument list to suppress warnings in in the caller
     * for generic varargs array construction.
     */

    public static <T, C extends java.util.Collection<E>, E> C filter(Collection<T> original,
        C result, Filter<T> f1, Filter<T> f2, Filter<T> f3, Filter<T> f4) {
        for (T t : original) {
            if ((f1 == null || f1.selects(t)) && (f2 == null || f2.selects(t))
             && (f3 == null || f3.selects(t)) && (f4 == null || f4.selects(t)))
                result.add((E) t);
        }
        return result;
    }

    /**
     * Applies chain of filters ANDed on the given collection to pick a single element.
     * A null filter evaluates always TRUE.
     * The arguments are not passed as variable argument list to suppress warnings in in the caller
     * for generic varargs array construction.
     */
    public static <T> T pick(Collection<T> original, Filter<T> f1, Filter<T> f2, Filter<T> f3,
        Filter<T> f4) {
        for (T t : original) {
            if ((f1 == null || f1.selects(t)) && (f2 == null || f2.selects(t))
             && (f3 == null || f3.selects(t)) && (f4 == null || f4.selects(t)))
                return t;
        }
        return null;
    }

    static <T, C extends java.util.Collection<E>, E> C filter(Collection<T> original,
            C result, Filter<T> f1) {
        return filter(original, result, f1, null, null, null);
    }

    static <T, C extends java.util.Collection<E>, E> C filter(Collection<T> original,
            C result, Filter<T> f1, Filter<T> f2) {
        return filter(original, result, f1, f2, null, null);
    }

    static <T, C extends java.util.Collection<E>, E> C filter(Collection<T> original,
            C result, Filter<T> f1, Filter<T> f2, Filter<T> f3) {
        return filter(original, result, f1, f2, f3, null);
    }

    static <T> T pick(Collection<T> original, Filter<T> f1) {
        return pick(original, f1, null, null, null);
    }

    static <T> T pick(Collection<T> original, Filter<T> f1, Filter<T> f2) {
        return pick(original, f1, f2, null, null);
    }

    static <T> T pick(Collection<T> original, Filter<T> f1, Filter<T> f2, Filter<T> f3) {
        return pick(original, f1, f2, f3, null);
    }


    /**
     * Affirms if the given attribute is a Singular attribute.
     *
     */
    public static final class SingularAttributeFilter<X> implements
            Filter<Attribute<? super X, ?>> {
        private final boolean _invert;

        public SingularAttributeFilter() {
            this(false);
        }

        public SingularAttributeFilter(boolean inverted) {
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            return _invert ? attr.isCollection() : !attr.isCollection();
        }

        @Override
        public SingularAttributeFilter<X> inverse() {
            return new SingularAttributeFilter<>(!_invert);
        }
    }

    public static final class DeclaredAttributeFilter<X> implements
            Filter<Attribute<? super X, ?>> {
        private final ManagedType<X> owner;
        private final boolean _invert;

        DeclaredAttributeFilter(ManagedType<X> owner) {
            this(owner, false);
        }

        DeclaredAttributeFilter(ManagedType<X> owner, boolean inverted) {
            this.owner = owner;
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            return _invert ? attr.getDeclaringType() != owner : attr
                    .getDeclaringType() == owner;
        }

        @Override
        public DeclaredAttributeFilter<X> inverse() {
            return new DeclaredAttributeFilter<>(owner, !_invert);
        }
    }

    /**
     * Selects if the attribute type matches the given Java class.
     * null matches any type.
     */
    public static final class AttributeTypeFilter<X, Y> implements Filter<Attribute<? super X, ?>> {
        private final Class<Y> _type;
        private final boolean _invert;

        public AttributeTypeFilter(Class<Y> type) {
            this(type, false);
        }

        public AttributeTypeFilter(Class<Y> type, boolean inverted) {
            _type = type;
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            boolean result = _type == null || Filters.canConvert(attr.getJavaType(), _type, false);
            return _invert ? !result : result;
        }

        @Override
        public AttributeTypeFilter<X, Y> inverse() {
            return new AttributeTypeFilter<>(_type, !_invert);
        }
    }

    public static final class AttributeNameFilter<X> implements
            Filter<Attribute<? super X, ?>> {
        private final String _name;
        private final boolean _invert;

        public AttributeNameFilter(String name) {
            this(name, false);
        }

        public AttributeNameFilter(String name, boolean inverted) {
            _name = name;
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            return _invert ? !attr.getName().equals(_name) : attr.getName()
                    .equals(_name);
        }

        @Override
        public AttributeNameFilter<X> inverse() {
            return new AttributeNameFilter<>(_name, !_invert);
        }
    }

    public static final class PluralCategoryFilter<X> implements
            Filter<Attribute<? super X, ?>> {
        private final CollectionType _category;
        private final boolean _invert;

        public PluralCategoryFilter(CollectionType category) {
            this(category, false);
        }

        public PluralCategoryFilter(CollectionType category, boolean inverted) {
            _category = category;
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            boolean result = (attr instanceof PluralAttribute<?, ?, ?>)
                    && ((PluralAttribute<?, ?, ?>) attr).getCollectionType() == _category;
            return _invert ? !result : result;
        }

        @Override
        public PluralCategoryFilter<X> inverse() {
            return new PluralCategoryFilter<>(_category, !_invert);
        }
    }

    /**
     * Selects plural attribute of given element type.
     * null element type imply <em>any</em> element type.
     */

    public static final class ElementTypeFilter<X, E> implements Filter<Attribute<? super X, ?>> {
        private final Class<E> _elementType;
        private final boolean _invert;

        public ElementTypeFilter(Class<E> eType) {
            this(eType, false);
        }

        public ElementTypeFilter(Class<E> eType, boolean inverted) {
            _elementType = eType;
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            boolean result = (attr instanceof PluralAttribute<?, ?, ?>)
                    && (_elementType == null
                    || ((PluralAttribute<?, ?, ?>) attr).getElementType().getJavaType()
                         == _elementType);
            return _invert ? !result : result;
        }

        @Override
        public ElementTypeFilter<X, E> inverse() {
            return new ElementTypeFilter<>(_elementType, !_invert);
        }
    }

    public static final class EntryTypeFilter<X, K, V> implements
            Filter<Attribute<? super X, ?>> {
        private final Class<K> _keyType;
        private final Class<V> _valueType;
        private final boolean _invert;

        public EntryTypeFilter(Class<K> kType, Class<V> vType) {
            this(kType, vType, false);
        }

        public EntryTypeFilter(Class<K> kType, Class<V> vType, boolean inverted) {
            _keyType = kType;
            _valueType = vType;
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            boolean result = (attr instanceof MapAttribute<?, ?, ?>)
                    && (_keyType == null
                    || ((MapAttribute<?, ?, ?>) attr).getKeyType().getJavaType() == _keyType)
                    && (_valueType == null
                    || ((MapAttribute<?, ?, ?>) attr).getElementType().getJavaType() == _valueType);
            return _invert ? !result : result;
        }

        @Override
        public EntryTypeFilter<X, K, V> inverse() {
            return new EntryTypeFilter<>(_keyType, _valueType, !_invert);
        }
    }

    public static final class IdAttributeFilter<X> implements
            Filter<Attribute<? super X, ?>> {
        private final boolean _invert;

        public IdAttributeFilter() {
            this(false);
        }

        public IdAttributeFilter(boolean inverted) {
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            boolean result = ((Members.Member<?, ?>) attr).fmd.isPrimaryKey();
            return _invert ? !result : result;
        }

        @Override
        public IdAttributeFilter<X> inverse() {
            return new IdAttributeFilter<>(!_invert);
        }
    }

    public static final class VersionAttributeFilter<X> implements Filter<Attribute<? super X, ?>> {
        private final boolean _invert;

        public VersionAttributeFilter() {
            this(false);
        }

        public VersionAttributeFilter(boolean inverted) {
            _invert = inverted;
        }

        @Override
        public boolean selects(Attribute<? super X, ?> attr) {
            FieldMetaData fmd = ((Members.Member<?, ?>) attr).fmd;
            boolean result = fmd.isVersion();
            return _invert ? !result : result;
        }

        @Override
        public IdAttributeFilter<X> inverse() {
            return new IdAttributeFilter<>(!_invert);
        }
    }

}
