org.odata4j.producer.inmemory.BeanModel.java Source code

Java tutorial

Introduction

Here is the source code for org.odata4j.producer.inmemory.BeanModel.java

Source

/**
 *
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.odata4j.producer.inmemory;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.core4j.Enumerable;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.mule.modules.odata.PropertyNamingFormat;
import org.odata4j.core.Guid;
import org.odata4j.core.Throwables;

/**
 * An abstract representation of the "bean" nature of a class.
 * This class caches up-front analysis of a class to locate the
 * getters and setters it will need in order to operate on instances.
 *
 * <p>Instances of this class can then be used in place of reflection.</p>
 */
public class BeanModel {

    private final Class<?> beanClass;
    private final Map<String, Method> getters;
    private final Map<String, Method> setters;
    private final Map<String, Class<?>> types;
    private final Map<String, Class<?>> collections;

    /**
     * Constructs the abstract bean representation of a class.
     *
     * @param beanClass  the class to introspect
     */
    public BeanModel(Class<?> beanClass) {
        this.beanClass = beanClass;
        this.getters = getBeanGetters(beanClass);
        this.setters = getBeanSetters(beanClass);
        this.types = computeTypes(getters, setters);
        this.collections = computeCollections(getters, setters);
    }

    /**
     * Recovers the original class on which this metadata is based.
     *
     * @return the original class
     */
    public Class<?> getBeanClass() {
        return beanClass;
    }

    /**
     * Returns the list of all properties identified on this class.
     *
     * <p>A property is any field that has a simple value type (i.e. not a collection type)
     * and either has a getter or a setter defined on it.
     *
     * @return the list of identified properties
     */
    public Iterable<String> getPropertyNames() {
        return types.keySet();
    }

    /**
     * Discovers the type of a property.
     *
     * @param propertyName  the property you are interested in
     * @return the type of the property
     */
    public Class<?> getPropertyType(String propertyName) {
        return types.get(propertyName);
    }

    /**
     * Returns the list of properties that have collection types.
     *
     * @return the list of properties
     */
    public Iterable<String> getCollectionNames() {
        return collections.keySet();
    }

    /**
     * For any given collection type, identifies the type of the elements of the collection.
     *
     * @param collectionName  the name of the collection
     * @return the type of the elements of the named collection
     */
    public Class<?> getCollectionElementType(String collectionName) {
        return collections.get(collectionName);
    }

    /**
     * Returns true if the property has a getter.
     */
    public boolean canRead(String propertyName) {
        return getters.containsKey(propertyName);
    }

    /**
     * Returns true if the property has a setter.
     */
    public boolean canWrite(String propertyName) {
        for (PropertyNamingFormat format : PropertyNamingFormat.values()) {
            if (setters.containsKey(format.toOData(propertyName))) {
                return true;
            }
        }

        return false;
    }

    /**
     * Interrogates an instance of the target class and discovers the value
     * of a given property.
     * This method is only intended to be used for simple properties.
     *
     * @param target  the instance of the class
     * @param propertyName  the name of the property to fetch
     * @return the value of the property in the given object
     */
    public Object getPropertyValue(Object target, String propertyName) {
        Method method = getGetter(propertyName);
        if (!method.isAccessible())
            method.setAccessible(true);
        try {
            return method.invoke(target);
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Updates an instance to set a property to a given value
     * This method is only intended to be used for simple properties
     *
     * @param target the instance to update
     * @param propertyName the name of the property
     * @param propertyValue the value to set in the property
     */
    public void setPropertyValue(Object target, String propertyName, Object propertyValue) {
        Method method = getSetter(propertyName);
        if (!method.isAccessible())
            method.setAccessible(true);
        try {

            // if a joda time, make sure the pojo also expects a joda time
            // otherwise try to set the value as a java.util.Date
            Class<?> argumentType = method.getParameterTypes()[0];
            if (propertyValue instanceof LocalDateTime) {
                if (argumentType.isAssignableFrom(LocalDateTime.class)) {
                    method.invoke(target, propertyValue);
                    return;
                } else if (argumentType.isAssignableFrom(Date.class)) {
                    LocalDateTime jodaDate = (LocalDateTime) propertyValue;
                    Date javaDate = new Date(jodaDate.toDateTime(DateTimeZone.UTC).getMillis());
                    method.invoke(target, javaDate);
                    return;
                }
            } else if (propertyValue instanceof Guid) {
                if (argumentType.isAssignableFrom(String.class)) {
                    Guid guid = (Guid) propertyValue;
                    propertyValue = guid.getValue();
                }
            }
            method.invoke(target, propertyValue);
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Returns a collection from a property in an instance.
     *
     * @param target  the instance to look at
     * @param collectionName  the name of the property on the instance which holds the collection
     * @return an iterable containing the elements of the collection
     */
    public Iterable<?> getCollectionValue(Object target, String collectionName) {
        Method method = getGetter(collectionName);
        if (!method.isAccessible())
            method.setAccessible(true);
        try {
            Object obj = method.invoke(target);
            if (obj == null)
                return null;
            else
                return obj.getClass().isArray() ? Enumerable.create((Object[]) obj) : (Iterable<?>) obj;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Updates a collection property.
     *
     * @param target  the instance to look at
     * @param collectionName  the name of the property on the instance which holds the collection
     * @param collectionValue  the new collection
     */
    public <T> void setCollectionValue(Object target, String collectionName, Collection<T> collectionValue) {
        Method method = getSetter(collectionName);
        if (!method.isAccessible())
            method.setAccessible(true);
        try {
            Object value = null;

            if (collectionValue != null) {
                Class<?> clazz = method.getParameterTypes()[0];
                if (List.class.isAssignableFrom(clazz)) {
                    value = collectionValue instanceof List ? (List<T>) collectionValue
                            : new ArrayList<T>(collectionValue);
                } else if (Set.class.isAssignableFrom(clazz)) {
                    value = collectionValue instanceof Set ? (Set<T>) collectionValue
                            : new HashSet<T>(collectionValue);
                } else
                    throw new RuntimeException("Unsupported collection type " + collectionValue.getClass());
            }

            method.invoke(target, value);
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }

    }

    private Method getGetter(String propertyName) {
        Method method = getters.get(propertyName);
        if (method == null)
            throw new IllegalArgumentException("No getter found for propertyName " + propertyName);
        return method;
    }

    private Method getSetter(String propertyName) {
        Method method = null;
        for (PropertyNamingFormat format : PropertyNamingFormat.values()) {
            method = setters.get(format.toOData(propertyName));
            if (method != null) {
                return method;
            }
        }

        throw new IllegalArgumentException("No setter found for propertyName " + propertyName);
    }

    private static Map<String, Class<?>> computeTypes(Map<String, Method> getters, Map<String, Method> setters) {
        Map<String, Class<?>> rt = new HashMap<String, Class<?>>();

        for (Entry<String, Method> getter : getters.entrySet()) {
            Class<?> getterType = getter.getValue().getReturnType();
            if (!isIterable(getterType))
                rt.put(getter.getKey(), getterType);
        }

        for (Entry<String, Method> setter : setters.entrySet()) {
            String propertyName = setter.getKey();
            Class<?> getterType = rt.get(propertyName);
            if (getterType != null) {
                Class<?> setterType = setter.getValue().getParameterTypes()[0];

                if (getterType != null && !getterType.equals(setterType))
                    throw new RuntimeException(
                            String.format("Inconsistent types for property %s.%s: getter type %s, setter type %s",
                                    setters.get(propertyName).getDeclaringClass().getName(), propertyName,
                                    getterType.getName(), setterType.getName()));

                rt.put(propertyName, setterType);
            }
        }

        return rt;
    }

    private Map<String, Class<?>> computeCollections(Map<String, Method> getters2, Map<String, Method> setters2) {
        Map<String, Class<?>> rt = new HashMap<String, Class<?>>();

        for (Entry<String, Method> getter : getters.entrySet()) {
            String propertyName = getter.getKey();
            Method method = getter.getValue();
            Class<?> getterType = method.getReturnType();
            if (isIterable(getterType)) {
                Class<?> setterType = setters.containsKey(propertyName)
                        ? setters.get(propertyName).getParameterTypes()[0]
                        : null;
                if (setterType != null) {
                    if (!getterType.equals(setterType))
                        throw new RuntimeException(String.format(
                                "Inconsistent types for association %s.%s: getter type %s, setter type %s",
                                setters.get(propertyName).getDeclaringClass().getName(), propertyName,
                                getterType.getName(), setterType.getName()));

                    Class<?> elementClass;
                    Type type = method.getGenericReturnType();
                    if (type instanceof ParameterizedType) {
                        Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                        elementClass = actualTypes.length > 0 ? (Class<?>) actualTypes[0] : Object.class;
                    } else
                        elementClass = Object.class;

                    rt.put(propertyName, elementClass);
                }
            }
        }

        return rt;
    }

    private static boolean isIterable(Class<?> clazz) {
        return clazz.isArray() || Iterable.class.isAssignableFrom(clazz);
    }

    private static Map<String, Method> getBeanGetters(Class<?> clazz) {

        Map<String, Method> rt = new HashMap<String, Method>();
        for (Method method : clazz.getMethods()) {
            String methodName = method.getName();
            if (methodName.startsWith("get") && methodName.length() > 3
                    && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0
                    && !method.getReturnType().equals(Void.TYPE) && !Modifier.isStatic(method.getModifiers())) {
                String name = methodName.substring(3);
                rt.put(name, method);
            }
            if (methodName.startsWith("is") && methodName.length() > 2
                    && Character.isUpperCase(methodName.charAt(2)) && method.getParameterTypes().length == 0
                    && (method.getReturnType().equals(Boolean.class) || method.getReturnType().equals(Boolean.TYPE))
                    && !Modifier.isStatic(method.getModifiers())) {
                String name = methodName.substring(2);
                rt.put(name, method);
            }
        }
        return rt;
    }

    private static Map<String, Method> getBeanSetters(Class<?> clazz) {

        Map<String, Method> rt = new HashMap<String, Method>();
        for (Method method : clazz.getMethods()) {
            String methodName = method.getName();
            if (methodName.startsWith("set") && methodName.length() > 3
                    && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 1
                    && method.getReturnType().equals(Void.TYPE) && !Modifier.isStatic(method.getModifiers())) {
                String name = methodName.substring(3);
                rt.put(name, method);
            }
        }
        return rt;
    }

}