org.dbg4j.core.adapters.impl.DefaultDebuggingAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.dbg4j.core.adapters.impl.DefaultDebuggingAdapter.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership. 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 org.dbg4j.core.adapters.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import org.dbg4j.core.adapters.DebuggingAdapter;
import org.dbg4j.core.adapters.EvaluationAdapter;
import org.dbg4j.core.adapters.MethodInvocationPoint;
import org.dbg4j.core.annotations.Adapter;
import org.dbg4j.core.annotations.Debug;
import org.dbg4j.core.annotations.Ignore;
import org.dbg4j.core.beans.DebugData;
import org.dbg4j.core.context.DebugContext;

import static org.dbg4j.core.DebugUtils.*;

/**
 * Default debugging adapter.
 *
 * @see DebuggingAdapter
 * @author Maksym Fedoryshyn
 */
public class DefaultDebuggingAdapter implements DebuggingAdapter {

    public static final String IGNORED_VALUE = "@Ignore";
    public static final String UNKNOWN_VALUE = "**unknown**";
    public static final String TYPE = "METHOD";

    /**
     * See {@link Debug} for evaluation rules.
     *
     * @param methodInvocationPoint
     */
    @Nullable
    @Override
    public Object debug(@Nonnull MethodInvocationPoint methodInvocationPoint) throws Throwable {
        if (!DebugContext.isDebugAllowed()) {
            return methodInvocationPoint.invoke();
        }

        DebugData data = createMainData(methodInvocationPoint);
        if (methodInvocationPoint.getDebugAnnotation().debugOnce()
                && DebugContext.getContext().contains(data, new DebuggingMethodComparator())) {
            return methodInvocationPoint.invoke();
        }

        appendArgumentsInfo(data, methodInvocationPoint);
        appendInstanceFieldsInfo(data, methodInvocationPoint);
        appendStackTraceInfo(data);

        Object result = null;
        Throwable error = null;
        try {
            result = methodInvocationPoint.invoke();
        } catch (Throwable throwable) {
            error = throwable;
        }

        if (error != null) {
            data.set("Error", ExceptionUtils.getStackTrace(error));
            DebugContext.getContext().addDebugRecord(data);
            throw error;
        } else {
            appendResultInfo(data, methodInvocationPoint, result);
        }

        DebugContext.getContext().addDebugRecord(data);

        return result;
    }

    protected DebugData createMainData(MethodInvocationPoint methodInvocationPoint) {
        DebugData data = new DebugData();

        data.set("Class", getClassName(methodInvocationPoint.getInstance()));
        data.set("Method", getMethodSignature(methodInvocationPoint.getMethod()));
        data.set("Type", TYPE);
        return data;
    }

    /**
     * Append debugged method arguments to debug data in declaration order. Arguments annotated with {@link Ignore}
     * annotation are not evaluated,
     * the string <code>"@Ignore"</code> is set instead their value (we cannot just ignore them b/c we should not break
     * method signature). By default parameter value evaluates by {@link DefaultEvaluationAdapter} unless another
     * evaluator is set by {@link Adapter} annotation.
     *
     * @see Ignore
     * @see Adapter
     * @see DefaultEvaluationAdapter
     */
    protected void appendArgumentsInfo(DebugData data, MethodInvocationPoint methodInvocationPoint) {

        if (methodInvocationPoint.getParameters() == null || methodInvocationPoint.getParameters().length == 0) {
            return;
        }

        Class[] parameterTypes = methodInvocationPoint.getMethod().getParameterTypes();
        if (parameterTypes == null || parameterTypes.length == 0) {
            return;
        }

        List<Pair<Class, String>> arguments = new ArrayList<Pair<Class, String>>();

        Annotation[][] parameterAnnotations = methodInvocationPoint.getMethod().getParameterAnnotations();

        for (int i = 0; i < parameterTypes.length; i++) {
            try {
                if (containsAnnotation(parameterAnnotations[i], Ignore.class)) {
                    arguments.add(ImmutablePair.of(parameterTypes[i], IGNORED_VALUE));
                    continue;
                }

                EvaluationAdapter evaluationAdapter = null;
                if (containsAnnotation(parameterAnnotations[i], Adapter.class)) {
                    Adapter adapterAnnotation = getAnnotation(parameterAnnotations[i], Adapter.class);
                    evaluationAdapter = adapterAnnotation.value().newInstance();
                }
                if (evaluationAdapter == null) {
                    evaluationAdapter = new DefaultEvaluationAdapter();
                }

                arguments.add(ImmutablePair.<Class, String>of(parameterTypes[i],
                        evaluationAdapter.evaluate(parameterTypes[i], methodInvocationPoint.getParameters()[i])));
            } catch (Exception ignored) {
            }
        }

        if (arguments.size() > 0) {
            List<DebugData> args = new ArrayList<DebugData>(arguments.size());
            for (Pair<Class, String> argument : arguments) {
                args.add(new DebugData(argument.getLeft().getSimpleName(), argument.getRight()));
            }
            data.set("Arguments", args);
        }
    }

    /**
     * See {@link Debug} for fields evaluation rules.
     * @param data
     * @param methodInvocationPoint
     */
    protected void appendInstanceFieldsInfo(DebugData data, MethodInvocationPoint methodInvocationPoint) {
        String[] fieldNamesFromAnnotation = methodInvocationPoint.getDebugAnnotation().instanceFields();
        if (fieldNamesFromAnnotation != null && fieldNamesFromAnnotation.length == 1
                && Debug.DEBUG_SKIP_FIELDS_CONSTANT.equals(fieldNamesFromAnnotation[0])) {
            return;
        }

        Collection<Field> fieldsForDebug = null;
        if (fieldNamesFromAnnotation != null) {
            if (fieldNamesFromAnnotation.length == 1
                    && Debug.DEBUG_ALL_FIELDS_CONSTANT.equals(fieldNamesFromAnnotation[0])) {
                fieldsForDebug = getObjectFields(methodInvocationPoint.getInstance(), Ignore.class, false);
            } else if (fieldNamesFromAnnotation.length == 1
                    && Debug.DEBUG_ANNOTATED_FIELDS_CONSTANT.equals(fieldNamesFromAnnotation[0])) {
                fieldsForDebug = getObjectFields(methodInvocationPoint.getInstance(), Debug.class, true);
            } else if (fieldNamesFromAnnotation.length > 0) {
                fieldsForDebug = getObjectFields(methodInvocationPoint.getInstance(), fieldNamesFromAnnotation);
            }
        }

        if (fieldsForDebug != null && fieldsForDebug.size() > 0) {
            Map<String, String> fields = getFieldValues(methodInvocationPoint.getInstance(), fieldsForDebug);
            DebugData d = new DebugData();
            for (Map.Entry<String, String> entry : fields.entrySet()) {
                d.set(entry.getKey(), entry.getValue());
            }
            data.set("Fields", d);
        }

    }

    /**
     * appends stacktrace info (answers question "Who called this method?")
     *
     * @param data
     */
    protected void appendStackTraceInfo(DebugData data) {
        try {
            throw new StackTraceException();
        } catch (StackTraceException e) {
            data.set("Stacktrace", ExceptionUtils.getStackTrace(e));
        }
    }

    /**
     * Appends method invocation result to the debug data. Appends nothing if method returns <code>void</code>. Takes
     * {@link Adapter} annotation into account during result evaluation.
     *
     * @param data
     * @param methodInvocationPoint
     * @param result
     * @see Debug
     * @see Adapter
     */
    protected void appendResultInfo(@Nonnull DebugData data, @Nonnull MethodInvocationPoint methodInvocationPoint,
            @Nullable Object result) {
        Method method = methodInvocationPoint.getMethod();
        if (method.getReturnType().equals(Void.TYPE)) {
            return;
        }

        String resultStr = UNKNOWN_VALUE;
        try {
            EvaluationAdapter evaluationAdapter = null;
            if (methodInvocationPoint.getMethod().isAnnotationPresent(Adapter.class)) {
                Adapter adapterAnnotation = methodInvocationPoint.getMethod().getAnnotation(Adapter.class);
                evaluationAdapter = adapterAnnotation.value().newInstance();
            }
            if (evaluationAdapter == null) {
                evaluationAdapter = new DefaultEvaluationAdapter();
            }
            resultStr = evaluationAdapter.evaluate(methodInvocationPoint.getMethod().getReturnType(), result);
        } catch (Exception ignored) {
        }

        data.set("Result", resultStr);
    }

}