Package org.mongodb.morphia.mapping

Source Code of org.mongodb.morphia.mapping.MappedField

package org.mongodb.morphia.mapping;


import com.mongodb.DBObject;
import org.mongodb.morphia.annotations.AlsoLoad;
import org.mongodb.morphia.annotations.ConstructorArgs;
import org.mongodb.morphia.annotations.Embedded;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import org.mongodb.morphia.annotations.NotSaved;
import org.mongodb.morphia.annotations.Property;
import org.mongodb.morphia.annotations.Reference;
import org.mongodb.morphia.annotations.Serialized;
import org.mongodb.morphia.annotations.Version;
import org.mongodb.morphia.logging.Logger;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.utils.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.util.Arrays.asList;


/**
* Represents the mapping of this field to/from mongodb (name, list<annotation>)
*
* @author Scott Hernandez
*/
public class MappedField {
    private static final Logger LOG = MorphiaLoggerFactory.get(MappedField.class);
    // The Annotations to look for when reflecting on the field (stored in the mappingAnnotations)
    private static final List<Class<? extends Annotation>> INTERESTING = new ArrayList<Class<? extends Annotation>>();

    static {
        INTERESTING.add(Serialized.class);
        INTERESTING.add(Indexed.class);
        INTERESTING.add(Property.class);
        INTERESTING.add(Reference.class);
        INTERESTING.add(Embedded.class);
        INTERESTING.add(Id.class);
        INTERESTING.add(Version.class);
        INTERESTING.add(ConstructorArgs.class);
        INTERESTING.add(AlsoLoad.class);
        INTERESTING.add(NotSaved.class);
    }

    private final Mapper mapper;
    private Class persistedClass;
    private Field field; // the field :)
    private Class realType; // the real type
    private Constructor constructor; // the constructor for the type
    // Annotations that have been found relevant to mapping
    private final Map<Class<? extends Annotation>, Annotation> foundAnnotations = new HashMap<Class<? extends Annotation>, Annotation>();
    private Type subType; // the type (T) for the Collection<T>/T[]/Map<?,T>
    private Type mapKeyType; // the type (T) for the Map<T,?>
    private boolean isSingleValue = true; // indicates the field is a single value
    private boolean isMongoType;
    // indicated the type is a mongo compatible type (our version of value-type)
    private boolean isMap; // indicated if it implements Map interface
    private boolean isSet; // indicated if the collection is a set
    //for debugging
    private boolean isArray; // indicated if it is an Array
    private boolean isCollection; // indicated if the collection is a list)

    MappedField(final Field f, final Class<?> clazz, final Mapper mapper) {
        this.mapper = mapper;
        f.setAccessible(true);
        field = f;
        persistedClass = clazz;
        discover();
    }

    //    protected MappedField() {
    //    }

    public static void addInterestingAnnotation(final Class<? extends Annotation> annotation) {
        INTERESTING.add(annotation);
    }

    /**
     * Discovers interesting (that we care about) things about the field.
     */
    protected void discover() {
        for (final Class<? extends Annotation> clazz : INTERESTING) {
            addAnnotation(clazz);
        }

        //type must be discovered before the constructor.
        realType = discoverType();
        constructor = discoverConstructor();
        discoverMultivalued();

        // check the main type
        isMongoType = ReflectionUtils.isPropertyType(realType);

        // if the main type isn't supported by the Mongo, see if the subtype is.
        // works for T[], List<T>, Map<?, T>, where T is Long/String/etc.
        if (!isMongoType && subType != null) {
            isMongoType = ReflectionUtils.isPropertyType(subType);
        }

        if (!isMongoType && !isSingleValue && (subType == null || subType.equals(Object.class))) {
            if (LOG.isWarningEnabled() && !mapper.getConverters().hasDbObjectConverter(this)) {
                LOG.warning(String.format("The multi-valued field '%s' is a possible heterogeneous collection. It cannot be verified. "
                                          + "Please declare a valid type to get rid of this warning. %s", getFullName(), subType));
            }
            isMongoType = true;
        }
    }

    private void discoverMultivalued() {
        if (realType.isArray()
            || Collection.class.isAssignableFrom(realType)
            || Map.class.isAssignableFrom(realType)) {

            isSingleValue = false;

            isMap = Map.class.isAssignableFrom(realType);
            isSet = Set.class.isAssignableFrom(realType);
            //for debugging
            isCollection = Collection.class.isAssignableFrom(realType);
            isArray = realType.isArray();

            //for debugging with issue
            if (!isMap && !isSet && !isCollection && !isArray) {
                throw new MappingException("type is not a map/set/collection/array : " + realType);
            }

            // get the subtype T, T[]/List<T>/Map<?,T>; subtype of Long[], List<Long> is Long
            subType = (realType.isArray()) ? realType.getComponentType() : ReflectionUtils.getParameterizedType(field, isMap ? 1 : 0);

            if (isMap) {
                mapKeyType = ReflectionUtils.getParameterizedType(field, 0);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Class discoverType() {
        Class type = field.getType();
        final Type gType = field.getGenericType();
        TypeVariable<GenericDeclaration> tv = null;
        ParameterizedType pt = null;
        if (gType instanceof TypeVariable) {
            tv = (TypeVariable<GenericDeclaration>) gType;
        } else if (gType instanceof ParameterizedType) {
            pt = (ParameterizedType) gType;
        }

        if (tv != null) {
            final Class typeArgument = ReflectionUtils.getTypeArgument(persistedClass, tv);
            if (typeArgument != null) {
                type = typeArgument;
            }
        } else if (pt != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("found instance of ParameterizedType : " + pt);
            }
        }

        if (Object.class.equals(realType) && (tv != null || pt != null)) {
            if (LOG.isWarningEnabled()) {
                LOG.warning(
                           "Parameterized types are treated as untyped Objects. See field '" + field.getName() + "' on "
                           + field.getDeclaringClass());
            }
        }

        if (type == null) {
            throw new MappingException("A type could not be found for " + field);
        }

        return type;
    }

    private Constructor discoverConstructor() {
        Constructor<?> constructor = null;
        Class<?> type = null;
        // get the first annotation with a concreteClass that isn't Object.class
        for (final Annotation an : foundAnnotations.values()) {
            try {
                final Method m = an.getClass().getMethod("concreteClass");
                m.setAccessible(true);
                final Object o = m.invoke(an);
                if (o != null && !(o.equals(Object.class))) {
                    type = (Class) o;
                    break;
                }
            } catch (NoSuchMethodException e) {
                // do nothing
            } catch (IllegalArgumentException e) {
                if (LOG.isWarningEnabled()) {
                    LOG.warning("There should not be an argument", e);
                }
            } catch (Exception e) {
                if (LOG.isWarningEnabled()) {
                    LOG.warning("", e);
                }
            }
        }

        if (type != null) {
            try {
                constructor = type.getDeclaredConstructor();
                constructor.setAccessible(true);
            } catch (NoSuchMethodException e) {
                if (!hasAnnotation(ConstructorArgs.class)) {
                    if (LOG.isWarningEnabled()) {
                        LOG.warning("No usable constructor for " + type.getName(), e);
                    }
                }
            }
        } else {
            // see if we can create instances of the type used for declaration
            type = getType();
            if (type != null) {
                try {
                    constructor = type.getDeclaredConstructor();
                    constructor.setAccessible(true);
                } catch (NoSuchMethodException e) {
                    // never mind.
                } catch (SecurityException e) {
                    // never mind.
                }
            }
        }
        return constructor;
    }

    /**
     * Returns the name of the field's (key)name for mongodb
     */
    public String getNameToStore() {
        return getMappedFieldName();
    }

    /**
     * Returns the name of the field's (key)name for mongodb, in order of loading.
     */
    public List<String> getLoadNames() {
        final List<String> names = new ArrayList<String>();
        names.add(getMappedFieldName());

        final AlsoLoad al = (AlsoLoad) foundAnnotations.get(AlsoLoad.class);
        if (al != null && al.value() != null && al.value().length > 0) {
            names.addAll(asList(al.value()));
        }

        return names;
    }

    /**
     * @return the value of this field mapped from the DBObject
     */
    public String getFirstFieldName(final DBObject dbObj) {
        String fieldName = getNameToStore();
        boolean foundField = false;
        for (final String n : getLoadNames()) {
            if (dbObj.containsField(n)) {
                if (!foundField) {
                    foundField = true;
                    fieldName = n;
                } else {
                    throw new MappingException(String.format("Found more than one field from @AlsoLoad %s", getLoadNames()));
                }
            }
        }
        return fieldName;
    }

    /**
     * @return the value from best mapping of this field
     */
    public Object getDbObjectValue(final DBObject dbObj) {
        return dbObj.get(getFirstFieldName(dbObj));
    }

    /**
     * Returns the name of the java field, as declared on the class
     */
    public String getJavaFieldName() {
        return field.getName();
    }

    /**
     * returns the annotation instance if it exists on this field
     */
    @SuppressWarnings("unchecked")
    public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
        return (T) foundAnnotations.get(clazz);
    }

    public Map<Class<? extends Annotation>, Annotation> getAnnotations() {
        return foundAnnotations;
    }

    /**
     * Indicates whether the annotation is present in the mapping (does not check the java field annotations, just the ones discovered)
     */
    public boolean hasAnnotation(final Class ann) {
        return foundAnnotations.containsKey(ann);
    }

    /**
     * Adds the annotation, if it exists on the field.
     */
    public void addAnnotation(final Class<? extends Annotation> clazz) {
        if (field.isAnnotationPresent(clazz)) {
            foundAnnotations.put(clazz, field.getAnnotation(clazz));
        }
    }

    /**
     * Adds the annotation, if it exists on the field.
     */
    public void addAnnotation(final Class<? extends Annotation> clazz, final Annotation ann) {
        foundAnnotations.put(clazz, ann);
    }

    /**
     * Adds the annotation even if not on the declared class/field.
     */
    public Annotation putAnnotation(final Annotation ann) {
        return foundAnnotations.put(ann.getClass(), ann);
    }

    /**
     * returns the full name of the class plus java field name
     */
    public String getFullName() {
        return field.getDeclaringClass().getName() + "." + field.getName();
    }

    /**
     * Returns the name of the field's key-name for mongodb
     */
    private String getMappedFieldName() {
        if (hasAnnotation(Id.class)) {
            return Mapper.ID_KEY;
        } else if (hasAnnotation(Property.class)) {
            final Property mv = (Property) foundAnnotations.get(Property.class);
            if (!mv.value().equals(Mapper.IGNORED_FIELDNAME)) {
                return mv.value();
            }
        } else if (hasAnnotation(Reference.class)) {
            final Reference mr = (Reference) foundAnnotations.get(Reference.class);
            if (!mr.value().equals(Mapper.IGNORED_FIELDNAME)) {
                return mr.value();
            }
        } else if (hasAnnotation(Embedded.class)) {
            final Embedded me = (Embedded) foundAnnotations.get(Embedded.class);
            if (!me.value().equals(Mapper.IGNORED_FIELDNAME)) {
                return me.value();
            }
        } else if (hasAnnotation(Serialized.class)) {
            final Serialized me = (Serialized) foundAnnotations.get(Serialized.class);
            if (!me.value().equals(Mapper.IGNORED_FIELDNAME)) {
                return me.value();
            }
        } else if (hasAnnotation(Version.class)) {
            final Version me = (Version) foundAnnotations.get(Version.class);
            if (!me.value().equals(Mapper.IGNORED_FIELDNAME)) {
                return me.value();
            }
        }

        return field.getName();
    }

    /**
     * returns the type of the underlying java field
     */
    public Class getType() {
        return realType;
    }

    /**
     * returns the declaring class of the java field
     */
    public Class getDeclaringClass() {
        return field.getDeclaringClass();
    }

    protected Class toClass(final Type t) {
        if (t == null) {
            return null;
        } else if (t instanceof Class) {
            return (Class) t;
        } else if (t instanceof GenericArrayType) {
            final Class type = (Class) ((GenericArrayType) t).getGenericComponentType();
            return Array.newInstance(type, 0).getClass();
        } else if (t instanceof ParameterizedType) {
            return (Class) ((ParameterizedType) t).getRawType();
        } else if (t instanceof WildcardType) {
            return (Class) ((WildcardType) t).getUpperBounds()[0];
        }

        throw new RuntimeException("Generic TypeVariable not supported!");

    }

    /**
     * If the java field is a list/array/map then the sub-type T is returned (ex. List<T>, T[], Map<?,T>
     */
    public Class getSubClass() {
        return toClass(subType);
    }

    public Type getSubType() {
        return subType;
    }

    void setSubType(final Type subType) {
        this.subType = subType;
    }

    public boolean isArray() {
        return isArray;
    }

    public boolean isSingleValue() {
        if (!isSingleValue && !isMap && !isSet && !isArray && !isCollection) {
            throw new RuntimeException("Not single, but none of the types that are not-single.");
        }
        return isSingleValue;
    }

    public boolean isMultipleValues() {
        return !isSingleValue();
    }

    public boolean isTypeMongoCompatible() {
        return isMongoType;
    }

    public boolean isMap() {
        return isMap;
    }

    void setIsMap(final boolean isMap) {
        this.isMap = isMap;
    }

    void setIsMongoType(final boolean isMongoType) {
        this.isMongoType = isMongoType;
    }

    public boolean isSet() {
        return isSet;
    }

    void setIsSet(final boolean isSet) {
        this.isSet = isSet;
    }

    /**
     * If the underlying java type is a map then it returns T from Map<T,V>
     */
    public Class getMapKeyClass() {
        return toClass(mapKeyType);
    }

    void setMapKeyType(final Class mapKeyType) {
        this.mapKeyType = mapKeyType;
    }

    /**
     * returns a constructor for the type represented by the field
     */
    public Constructor getCTor() {
        return constructor;
    }

    /**
     * Returns the value stored in the java field
     */
    public Object getFieldValue(final Object classInst) {
        try {
            field.setAccessible(true);
            return field.get(classInst);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets the value for the java field
     */
    public void setFieldValue(final Object classInst, final Object value) {
        try {
            field.setAccessible(true);
            field.set(classInst, value);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * returned the underlying java field
     */
    public Field getField() {
        return field;
    }

    public Class getConcreteType() {
        final Embedded e = getAnnotation(Embedded.class);
        if (e != null) {
            final Class concrete = e.concreteClass();
            if (concrete != Object.class) {
                return concrete;
            }
        }

        final Property p = getAnnotation(Property.class);
        if (p != null) {
            final Class concrete = p.concreteClass();
            if (concrete != Object.class) {
                return concrete;
            }
        }
        return getType();
    }

    public Mapper getMapper() {
        return mapper;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append(getMappedFieldName()).append(" (");
        sb.append(" type:").append(realType.getSimpleName()).append(",");

        if (isSingleValue()) {
            sb.append(" single:true,");
        } else {
            sb.append(" multiple:true,");
            sb.append(" subtype:").append(getSubClass()).append(",");
        }
        if (isMap()) {
            sb.append(" map:true,");
            if (getMapKeyClass() != null) {
                sb.append(" map-key:").append(getMapKeyClass().getSimpleName());
            } else {
                sb.append(" map-key: class unknown! ");
            }
        }

        if (isSet()) {
            sb.append(" set:true,");
        }
        if (isCollection) {
            sb.append(" collection:true,");
        }
        if (isArray) {
            sb.append(" array:true,");
        }

        //remove last comma
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.setLength(sb.length() - 1);
        }

        sb.append("); ").append(foundAnnotations.toString());
        return sb.toString();
    }
}
TOP

Related Classes of org.mongodb.morphia.mapping.MappedField

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.