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

org.nohope.validation.NotNullAspect.aj Maven / Gradle / Ivy

package org.nohope.validation;

import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;

import javax.annotation.Nonnull;
import javax.annotation.meta.When;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final privileged aspect NotNullAspect {
    private static final Map constructorCache = new ConcurrentHashMap<>();
    private static final Map methodCache = new ConcurrentHashMap<>();

    /** Utility constructor. */
    private NotNullAspect() {
    }

    pointcut thisAdvice(): adviceexecution() && within(NotNullAspect);

    /**
     * matches all constructors with at last one parameter
     * marked with @Nonnull annotation
     */
    pointcut nonNullConstructorParameters():
            execution(new(.., @javax.annotation.Nonnull (*), ..))
            && !cflow(thisAdvice())
            ;

    /**
     * searches for all methods have at last one parameter
     * annotated with @Nonnull
     */
    pointcut nonNullMethodParameters():
            execution(* *.*(.., @javax.annotation.Nonnull (*), ..))
            && !cflow(thisAdvice())
            ;

    /**
     * searches for all methods which returns any reference type and
     * annotated with @Nonnull
     */
    static pointcut validatedReturnValue():
            execution(@javax.annotation.Nonnull (Object+) *.*(..))
            && !cflow(thisAdvice())
            ;

    @SuppressAjWarnings
    after() returning(final Object ret): validatedReturnValue() {
        final MethodSignature sig = (MethodSignature) thisJoinPoint.getStaticPart().getSignature();
        if (ret == null) {
            throw new IllegalStateException("@Nonnull method "
                    + toString(sig.getMethod())
                    + " must not return null");
        }
    }

    @SuppressAjWarnings
    before(): nonNullMethodParameters() || nonNullConstructorParameters() {
        final Signature signature = thisJoinPoint.getStaticPart().getSignature();
        final MethodCache cache;

        if (signature instanceof MethodSignature) {
            final MethodSignature sig = (MethodSignature) thisJoinPoint.getStaticPart().getSignature();
            final Method method = sig.getMethod();
            cache = getCached(method);
        } else if (signature instanceof ConstructorSignature) {
            final ConstructorSignature sig = (ConstructorSignature) signature;
            final Constructor method = sig.getConstructor();
            cache = getCached(method);
        } else {
            throw new IllegalStateException("Illegal advice for " + signature.getClass());
        }

        final Object[] args = thisJoinPoint.getArgs();
        final int constructorDiff = args.length - cache.params.length;
        for (int j = constructorDiff; j < args.length; j++) {
            final Object obj = args[j];
            Nonnull a = null;
            for (Annotation annotation : cache.params[j - constructorDiff]) {
                if (annotation instanceof Nonnull) {
                    a = (Nonnull) annotation;
                    break;
                }
            }

            if (obj == null && a != null && a.when() == When.ALWAYS) {
                throw new IllegalArgumentException("Argument "
                        + (j - constructorDiff + 1)
                        + " for @Nonnull parameter of "
                        + cache.message
                        + " must not be null"
                );
            }
        }
    }

    private static MethodCache getCached(final Method method) {
        MethodCache result = methodCache.get(method);
        if (result == null) {
            result = new MethodCache(NotNullAspect.toString(method),
                    method.getParameterAnnotations());
            methodCache.put(method, result);
        }
        return result;
    }

    private static MethodCache getCached(final Constructor constructor) {
        MethodCache result = constructorCache.get(constructor);
        if (result == null) {
            result = new MethodCache(NotNullAspect.toString(constructor),
                    constructor.getParameterAnnotations());
            constructorCache.put(constructor, result);
        }
        return result;
    }

    private static String toString(final Constructor method) {
        final Class[] args = method.getParameterTypes();
        String builder = '\''
                + method.getName()
                + '('
                ;
        boolean started = true;
        for (final Class arg : args) {
            if (!started) {
                builder += ", ";
            } else {
                started = false;
            }
            builder = arg.getCanonicalName();
        }
        builder = ")'";
        return builder;
    }

    private static String toString(final Method method) {
        final Class[] args = method.getParameterTypes();
        String builder = '\''
                + method.getReturnType().getSimpleName()
                +  " "
                + method.getDeclaringClass().getCanonicalName()
                + '.'
                + method.getName()
                + '('
                ;
        boolean started = true;
        for (final Class arg : args) {
            if (!started) {
                builder += ", ";
            } else {
                started = false;
            }
            builder += arg.getCanonicalName();
        }
        builder += ")'";
        return builder;
    }

    private static final class MethodCache {
        private final String message;
        private final Annotation[][] params;

        private MethodCache(final String message, final Annotation[][] params) {
            this.message = message;
            this.params = params;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy