package com.github.tomschi.commons.aspect.logging;

import com.github.tomschi.commons.annotation.logging.LogExecution;
import com.github.tomschi.commons.slf4j.Slf4jUtils;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * The aspect MethodLogger logs the execution time of methods. For this
 * purpose the target method must be annotated with {@link LogExecution}
 * annotation.
 *
 * <br/>
 *
 * It is also possible to annotate the class with {@link LogExecution}.
 * Then all public methods of the class are logged.
 *
 * @author Tomschi
 * @since 0.1.1
 */
@Aspect
public class MethodLogger {

    private final static String logMessage = "Method {} needs {} to execute.";

    /**
     * Returns the {@link LogExecution} of the called method. When
     * called method hasn't a annotation the class annotation is returned.
     *
     * @param clazz The {@link Class} of the called method.
     * @param method The called {@link Method}.
     * @return The {@link LogExecution} annotation.
     */
    private LogExecution getAnnotation(Class<?> clazz, Method method) {
        LogExecution annotation = method.getAnnotation(LogExecution.class);
        if (annotation == null) {
            annotation = clazz.getAnnotation(LogExecution.class);
        }
        return annotation;
    }

    /**
     * Checks if the mehtod execution time should be logged. This method
     * compares the {@link LogExecution#limit()} and the given execution time.
     *
     * <br/>
     *
     * If {@link LogExecution#limit()} is less than the given execution time
     * the method returns <code>true</code>, else <code>false</code>.
     *
     * @param annotation A {@link LogExecution} annotation.
     * @param executionTimeMillis The execution time in millis.
     * @return <code>True</code>, if {@link LogExecution#limit()} is less than
     *         executionTimeMillis, else <code>false</code>
     */
    private boolean shouldLog(LogExecution annotation, long executionTimeMillis) {
        long limit = annotation.limit();
        TimeUnit limitTimeUnit = annotation.timeUnit();

        switch (limitTimeUnit) {
            case NANOSECONDS:
                return limit < TimeUnit.MILLISECONDS.toNanos(executionTimeMillis);
            case MICROSECONDS:
                return limit < TimeUnit.MILLISECONDS.toMicros(executionTimeMillis);
            case MILLISECONDS:
                return limit < executionTimeMillis;
            case SECONDS:
                return limit < TimeUnit.MILLISECONDS.toSeconds(executionTimeMillis);
            case MINUTES:
                return limit < TimeUnit.MILLISECONDS.toMinutes(executionTimeMillis);
            case HOURS:
                return limit < TimeUnit.MILLISECONDS.toHours(executionTimeMillis);
            case DAYS:
                return limit < TimeUnit.MILLISECONDS.toDays(executionTimeMillis);
            default:
                return false;
        }
    }

    /**
     * This method is marked as {@link Pointcut}. It checks, if the called
     * method is a public method.
     */
    @Pointcut("execution(public * *(..))")
    public void publicMethod() {}

    /**
     * This method is marked as {@link Pointcut}. It checks, if the class
     * of the called method is annotated with {@link LogExecution}.
     */
    @Pointcut("@within(com.github.tomschi.commons.annotation.logging.LogExecution)")
    public void annotatedBean() {}

    /**
     * This method is marked as {@link Pointcut}. It checks, if the called
     * method is annotated with {@link LogExecution}
     */
    @Pointcut("@annotation(com.github.tomschi.commons.annotation.logging.LogExecution)")
    public void annotatedMethod() {}

    /**
     * This method is called, when a method is annotated with {@link LogExecution} or
     * a class is annotated with {@link LogExecution} and the called method is public.
     *
     * <br/>
     *
     * The method calculates the execution time of the called method. For this purpose
     * the {@link StopWatch} is used. It will be only logged, if the execution time is
     * greater than the {@link LogExecution#limit()} and the {@link Logger} is enabled
     * for the class. The message will be logged with the {@link Logger} of the class, whose
     * method is called.
     *
     * @param joinPoint The {@link ProceedingJoinPoint}.
     * @return The method result of the called method.
     * @throws Throwable If error occurs.
     */
    @Around("(publicMethod() && annotatedBean()) || annotatedMethod()")
    public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> clazz = joinPoint.getTarget().getClass();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        LogExecution annotation = getAnnotation(clazz, method);
        Logger logger = LoggerFactory.getLogger(clazz);

        Object methodResult;

        if (!Slf4jUtils.isEnabled(logger, annotation.value())) {
            methodResult = joinPoint.proceed();
        } else {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            methodResult = joinPoint.proceed();
            stopWatch.stop();

            if (shouldLog(annotation, stopWatch.getTime())) {
                Slf4jUtils.log(logger, annotation.value(), logMessage, method.getName(), DurationFormatUtils.formatDurationHMS(stopWatch.getTime()));
            }
        }

        return methodResult;
    }

}

@@ -798,17 +798,16 @@
 *%0A * %3Cbr
-/
 %3E%0A *%0A * 
@@ -1872,33 +1872,32 @@
     *%0A     * %3Cbr
-/
 %3E%0A     *%0A     * 
@@ -4292,17 +4292,16 @@
    * %3Cbr
-/
 %3E%0A     *

Remove self-closing html element.