All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mobsandgeeks.saripaar.Reflector Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
/*
 * Copyright (C) 2014 Mobs & Geeks
 *
 * Licensed 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 com.mobsandgeeks.saripaar;

import android.view.View;

import com.mobsandgeeks.saripaar.adapter.ViewDataAdapter;
import com.mobsandgeeks.saripaar.annotation.ValidateUsing;
import com.mobsandgeeks.saripaar.exception.SaripaarViolationException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * Contains reflection methods that are helpful for introspection and retrieval of frequently used
 * methods and attributes.
 *
 * @author Ragunath Jawahar {@literal }
 * @since 2.0
 */
final class Reflector {

    /**
     * Retrieves the attribute method of the given {@link java.lang.annotation.Annotation}.
     *
     * @param annotationType  The {@link java.lang.annotation.Annotation}
     *      {@link java.lang.Class} to check.
     * @param attributeName  Attribute name.
     *
     * @return The {@link java.lang.reflect.Method} if the attribute is present,
     *      null otherwise.
     */
    public static Method getAttributeMethod(final Class annotationType,
            final String attributeName) {
        Method attributeMethod = null;
        try {
            attributeMethod = annotationType.getMethod(attributeName);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return attributeMethod;
    }

    /**
     * Retrieve an attribute value from an {@link java.lang.annotation.Annotation}.
     *
     * @param annotation  An {@link java.lang.annotation.Annotation} instance.
     * @param attributeName  Attribute name.
     * @param attributeDataType  {@link java.lang.Class} representing the attribute data type.
     * @param   Attribute value type.
     *
     * @return The attribute value.
     */
    @SuppressWarnings("unchecked")
    public static  T getAttributeValue(final Annotation annotation, final String attributeName,
            final Class attributeDataType) {

        T attributeValue = null;
        Class annotationType = annotation.annotationType();
        Method attributeMethod = getAttributeMethod(annotationType, attributeName);

        if (attributeMethod == null) {
            String message = String.format("Cannot find attribute '%s' in annotation '%s'.",
                    attributeName, annotationType.getName());
            throw new IllegalStateException(message);
        } else {
            try {
                Object result = attributeMethod.invoke(annotation);
                attributeValue = attributeDataType.isPrimitive()
                        ? (T) result
                        : attributeDataType.cast(result);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

        return attributeValue;
    }

    /**
     * Checks if an annotation was annotated with the given annotation.
     *
     * @param inspected  The {@link java.lang.annotation.Annotation} to be checked.
     * @param expected  The {@link java.lang.annotation.Annotation} that we are looking for.
     *
     * @return true if the annotation is present, false otherwise.
     */
    public static boolean isAnnotated(final Class inspected,
            final Class expected) {
        boolean isAnnotated = false;
        Annotation[] declaredAnnotations = inspected.getDeclaredAnnotations();
        for (Annotation declaredAnnotation : declaredAnnotations) {
            isAnnotated = expected.equals(declaredAnnotation.annotationType());
            if (isAnnotated) {
                break;
            }
        }
        return isAnnotated;
    }

    /**
     * Finds and returns the correct
     * {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter#getData(android.view.View)}
     * {@link java.lang.reflect.Method}.
     *
     * @param dataAdapterType  The {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}
     *      class whose {@code getData(View)} method is required.
     *
     * @return The correct {@code getData(View)} method.
     */
    public static Method findGetDataMethod(final Class dataAdapterType) {
        Method getDataMethod = null;
        Method[] declaredMethods = dataAdapterType.getDeclaredMethods();

        for (Method method : declaredMethods) {
            boolean methodNameIsGetData = "getData".equals(method.getName());

            if (methodNameIsGetData) {
                // If we don't declare a throws clause in the method signature, the compiler
                // creates an auto-generated volatile method with the java.lang.Object return type.
                int modifiers = method.getModifiers();
                boolean nonVolatile = !Modifier.isVolatile(modifiers);

                // Single 'View' parameter
                Class[] parameterTypes = method.getParameterTypes();
                boolean hasSingleViewParameter = parameterTypes.length == 1
                        && View.class.isAssignableFrom(parameterTypes[0]);

                if (nonVolatile && hasSingleViewParameter) {
                    getDataMethod = method;
                    break;
                }
            }
        }
        return getDataMethod;
    }

    /**
     * Instantiates a {@link AnnotationRule} object for the given type.
     *
     * @param ruleType  The {@link AnnotationRule} class to be instantiated.
     * @param ruleAnnotation  The rule {@link java.lang.annotation.Annotation} associated with
     *      the {@link AnnotationRule}.
     *
     * @return The instantiated {@link AnnotationRule} object.
     *
     * @throws SaripaarViolationException if {@link AnnotationRule} does not
     *      have a single-argument constructor that accepts a rule
     *      {@link java.lang.annotation.Annotation} instance.
     */
    public static AnnotationRule instantiateRule(final Class ruleType,
            final Annotation ruleAnnotation, final ValidationContext validationContext)
                    throws SaripaarViolationException {
        AnnotationRule rule = null;

        try {
            if (ContextualAnnotationRule.class.isAssignableFrom(ruleType)) {
                Constructor constructor = ruleType.getDeclaredConstructor(
                        ValidationContext.class, ruleAnnotation.annotationType());
                constructor.setAccessible(true);
                rule = (AnnotationRule) constructor.newInstance(validationContext, ruleAnnotation);
            } else if (AnnotationRule.class.isAssignableFrom(ruleType)) {
                Constructor constructor = ruleType.getDeclaredConstructor(
                        ruleAnnotation.annotationType());
                constructor.setAccessible(true);
                rule = (AnnotationRule) constructor.newInstance(ruleAnnotation);
            }
        } catch (NoSuchMethodException e) {
            String message = getMissingConstructorErrorMessage(ruleType,
                    ruleAnnotation.annotationType());
            throw new SaripaarViolationException(message);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return rule;
    }

    /**
     * Method finds the data type of the {@link AnnotationRule} that is tied up to the given rule
     * annotation.
     *
     * @param ruleAnnotation  Rule {@link java.lang.annotation.Annotation}.
     *
     * @return The expected data type for the
     *      {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}s.
     */
    public static Class getRuleDataType(final Annotation ruleAnnotation) {
        ValidateUsing validateUsing = getValidateUsingAnnotation(ruleAnnotation.annotationType());
        return getRuleDataType(validateUsing);
    }

    /**
     * Method finds the data type of the {@link AnnotationRule} that is tied up to the given rule
     * annotation.
     *
     * @param validateUsing  The {@link com.mobsandgeeks.saripaar.annotation.ValidateUsing}
     *      instance.
     *
     * @return The expected data type for the
     *      {@link com.mobsandgeeks.saripaar.adapter.ViewDataAdapter}s.
     */
    public static Class getRuleDataType(final ValidateUsing validateUsing) {
        Class rule = validateUsing.value();
        Method[] methods = rule.getDeclaredMethods();
        return getRuleTypeFromIsValidMethod(rule, methods);
    }

    /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     *  Private Methods
     * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */
    private static ValidateUsing getValidateUsingAnnotation(
            final Class annotationType) {
        ValidateUsing validateUsing = null;

        Annotation[] declaredAnnotations = annotationType.getDeclaredAnnotations();
        for (Annotation annotation : declaredAnnotations) {
            if (ValidateUsing.class.equals(annotation.annotationType())) {
                validateUsing = (ValidateUsing) annotation;
                break;
            }
        }
        return validateUsing;
    }

    private static String getMissingConstructorErrorMessage(
            final Class ruleType,
            final Class annotationType) {

        String message = null;
        if (ContextualAnnotationRule.class.isAssignableFrom(ruleType)) {
            message = String.format("A constructor accepting a '%s' and a '%s' is required for %s.",
                    ValidationContext.class, annotationType.getName(),
                    ruleType.getClass().getName());
        } else if (AnnotationRule.class.isAssignableFrom(ruleType)) {
            message = String.format(
                    "'%s' should have a single-argument constructor that accepts a '%s' instance.",
                    ruleType.getName(), annotationType.getName());
        }

        return message;
    }

    private static Class getRuleTypeFromIsValidMethod(final Class rule,
            final Method[] methods) {

        Class returnType = null;
        for (Method method : methods) {
            Class[] parameterTypes = method.getParameterTypes();

            if (matchesIsValidMethodSignature(method, parameterTypes)) {
                // This will be null, if there are no matching methods
                // in the class with a similar signature.
                if (returnType != null) {
                    String message = String.format(
                            "Found duplicate 'boolean isValid(T)' method signature in '%s'.",
                            rule.getName());
                    throw new SaripaarViolationException(message);
                }
                returnType = parameterTypes[0];
            }
        }
        return returnType;
    }

    private static boolean matchesIsValidMethodSignature(final Method method,
            final Class[] parameterTypes) {
        int modifiers = method.getModifiers();

        boolean isPublic = Modifier.isPublic(modifiers);
        boolean nonVolatile = !Modifier.isVolatile(modifiers);
        boolean returnsBoolean = Boolean.TYPE.equals(method.getReturnType());
        boolean matchesMethodName = "isValid".equals(method.getName());
        boolean hasSingleParameter = parameterTypes.length == 1;

        return isPublic && nonVolatile && returnsBoolean && matchesMethodName && hasSingleParameter;
    }

    private Reflector() {
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy