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

com.fitbur.testify.analyzer.CutClassAnalyzer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Sharmarke Aden.
 *
 * 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.fitbur.testify.analyzer;

import com.fitbur.asm.ClassVisitor;
import com.fitbur.asm.FieldVisitor;
import com.fitbur.asm.MethodVisitor;
import static com.fitbur.asm.Opcodes.ASM5;
import com.fitbur.asm.Type;
import static com.fitbur.asm.Type.getMethodType;
import static com.fitbur.guava.common.base.Preconditions.checkState;
import com.fitbur.testify.TestContext;
import com.fitbur.testify.descriptor.CutDescriptor;
import com.fitbur.testify.descriptor.DescriptorKey;
import com.fitbur.testify.descriptor.FieldDescriptor;
import com.fitbur.testify.descriptor.ParameterDescriptor;
import static java.lang.Class.forName;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import static java.util.stream.Stream.of;

/**
 * A class visitor implementation that performs analysis on the class under
 * test.
 *
 * @author saden
 */
public class CutClassAnalyzer extends ClassVisitor {

    public static final String CONSTRUCTOR_NAME = "";

    private final TestContext context;
    private int constCount = 0;
    private int fieldCount = 0;

    public CutClassAnalyzer(TestContext context) {
        super(ASM5);
        this.context = context;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        CutDescriptor cutDescriptor = context.getCutDescriptor();
        Class type = cutDescriptor.getType();
        try {
            Field field = type.getDeclaredField(name);

            DescriptorKey key = new DescriptorKey(field.getGenericType(), field.getName());
            FieldDescriptor fieldDescriptor = new FieldDescriptor(field, fieldCount++);
            cutDescriptor.putFieldDescriptor(key, fieldDescriptor);
        } catch (NoSuchFieldException e) {
            checkState(false,
                    "Field '%s' not found in class under test '%s'.\n%s",
                    name, type.getSimpleName(), e.getMessage());
        }

        return null;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (CONSTRUCTOR_NAME.equals(name)) {
            constCount++;

            AccessController.doPrivileged((PrivilegedAction) () -> {
                Type type = getMethodType(desc);
                Class[] parameterTypes = of(type.getArgumentTypes())
                        .sequential()
                        .map(this::getClass)
                        .toArray(Class[]::new);

                CutDescriptor cutDescriptor = context.getCutDescriptor();

                try {
                    Constructor constructor = cutDescriptor
                            .getField()
                            .getType()
                            .getDeclaredConstructor(parameterTypes);

                    cutDescriptor.setConstructor(constructor);

                    Parameter[] parameters = constructor.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        ParameterDescriptor paramDescriptor = new ParameterDescriptor(parameters[i], i);
                        context.addParameterDescriptor(paramDescriptor);
                    }

                } catch (NoSuchMethodException | SecurityException e) {
                    checkState(false,
                            "Constructor with '%s' parameters not accessible in '%s' class.",
                            Arrays.toString(parameterTypes), cutDescriptor.getTypeName());
                }
                return null;
            });
        }

        return null;
    }

    @Override
    public void visitEnd() {
        context.setConstructorCount(constCount);
        context.getCutDescriptor().setFieldCount(fieldCount);
    }

    private Class getClass(Type type) {
        try {
            return forName(type.getInternalName().replace('/', '.'));
        } catch (ClassNotFoundException e) {
            checkState(false, "Class '%s' not found in the classpath.", type.getClassName());
            //not reachable;
            throw new IllegalStateException(e);
        }
    }

}