/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.util;

import lombok.experimental.UtilityClass;

import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * Helper functions for checking reflective member features
 *
 * @author ranides
 */
@UtilityClass
public final class MemberTraits {
    
    private static final int ACCESS_ANY = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;

    /**
     * Returns true if member has "volatile" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isVolatile(Member member) {
        return Modifier.isVolatile(member.getModifiers());
    }

    /**
     * Returns true if member has "transient" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isTransient(Member member) {
        return Modifier.isTransient(member.getModifiers());
    }

    /**
     * Returns true if member has "synchronized" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isSynchronized(Member member) {
        return Modifier.isSynchronized(member.getModifiers());
    }

    /**
     * Returns true if member has "strict" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isStrict(Member member) {
        return Modifier.isStrict(member.getModifiers());
    }

    /**
     * Returns true if member has "static" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isStatic(Member member) {
        return Modifier.isStatic(member.getModifiers());
    }

    /**
     * Returns true if member has "public" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isPublic(Member member) {
        return Modifier.isPublic(member.getModifiers());
    }

    /**
     * Returns true if member has "protected" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isProtected(Member member) {
        return Modifier.isProtected(member.getModifiers());
    }

    /**
     * Returns true if member has "package" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isPackage(Member member) {
        return 0 == (member.getModifiers() & ACCESS_ANY);
    }

    /**
     * Returns true if member has "private" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isPrivate(Member member) {
        return Modifier.isPrivate(member.getModifiers());
    }

    /**
     * Returns true if member has "native" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isNative(Member member) {
        return Modifier.isNative(member.getModifiers());
    }

    /**
     * Returns true if member has "final" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isFinal(Member member) {
        return Modifier.isFinal(member.getModifiers());
    }

    /**
     * Returns true if member has "abstract" attribute
     *
     * @param member member
     * @return boolean
     */
    public static boolean isAbstract(Member member) {
        return Modifier.isAbstract(member.getModifiers());
    }
    
    /**
     * Returns true if provided method can be used as "getter".
     * Accepts getters with a name "getValue".
     * For boolean type accepts "isEnabled" form.
     *
     * @param method method
     * @return boolean
     */
    public static boolean isGetter(Member method) { 
        return (method instanceof Method) && isGetter((Method)method);
    }
    
    @SuppressWarnings("PMD")
    private static boolean isGetter(Method method) {
        final Class<?> ctype = method.getDeclaringClass();
        final Class<?> rtype = method.getReturnType();
        
        // eliminacja metod wbudowanych, w szczególności Object#getClass
        if(Object.class.equals(ctype)) {
            return false;
        }
        // eliminacja na podstawie deklaracji
        if(method.getParameterTypes().length != 0) {
            return false;
        }
        if(Void.class.equals(rtype) || void.class.equals(rtype)) {
            return false;
        }
        String name = method.getName();
        // akceptacja klasycznego gettera
        if(name.startsWith("get") && name.length()>3 && Character.isUpperCase(name.charAt(3)) ) {
            return true;
        }
        // akceptacja gettera logicznego "isValue"
        if(name.startsWith("is") && name.length()>2 && Character.isUpperCase(name.charAt(2)) && ClassTraits.isBool(rtype) ) {
            return true;
        }
        return false;
    }
    
    /**
     * Returns true if provided method can be used as "setter".
     *
     * @param method method
     * @return boolean
     */
    public static boolean isSetter(Member method) { 
        return (method instanceof Method) && isSetter((Method)method);
    }
    
    private static boolean isSetter(Method method) {
        final Class<?> ctype = method.getDeclaringClass();
        final Class<?> rtype = method.getReturnType(); //NOPMD
        
        // eliminacja metod wbudowanych
        if(Object.class.equals(ctype)) {
            return false;
        }
        // eliminacja na podstawie deklaracji
        if(method.getParameterTypes().length != 1) {
            return false;
        }
        if(!Void.class.equals(rtype) && !void.class.equals(rtype) && !ctype.isAssignableFrom(rtype)) {
            return false;
        }
        String name = method.getName();
        // akceptacja klasycznego settera
        if(name.startsWith("set") && name.length()>3 && Character.isUpperCase(name.charAt(3)) ) {
            return true;
        }
        return false;
    }
    
}
