jmockit.assist.MockASTVisitor.java Source code

Java tutorial

Introduction

Here is the source code for jmockit.assist.MockASTVisitor.java

Source

/*
 * Copyright (c) 2012 Andrejs Jermakovics.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Andrejs Jermakovics - initial implementation
 */
package jmockit.assist;

import static org.eclipse.jdt.core.dom.Modifier.isPrivate;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;

@SuppressWarnings("restriction")
public final class MockASTVisitor extends ASTVisitor {
    private final ICompilationUnit icunit;
    private CompilationUnit cu;
    private boolean hasMockitImport = false;

    private final List<CategorizedProblem> probs = new ArrayList<CategorizedProblem>();

    public MockASTVisitor(final ICompilationUnit cunitPar) {
        icunit = cunitPar;
    }

    @Override
    public boolean visit(final CompilationUnit node) {
        cu = node;
        return true;
    }

    @Override
    public void endVisit(final CompilationUnit node) {
        super.endVisit(node);
    }

    @Override
    public boolean visit(final MethodDeclaration node) {
        IMethodBinding meth = node.resolveBinding();
        ITypeBinding mockedType = MockUtil.findMockedType(node, meth); // new MockUp< MockedType >

        if (mockedType != null) {
            boolean hasMockAnn = MockUtil.isMockMethod(meth);
            boolean isClassInitMock = MockUtil.isClassInitializerMock(meth);
            boolean methodExists = isClassInitMock || findRealMethod(node, meth, mockedType) != null;

            if (!hasMockAnn && methodExists) {
                addMarker(node.getName(), "Mocked method missing @Mock annotation", false);
            }

            if (hasMockAnn && !methodExists) {
                addMarker(node.getName(), "Mocked real method not found in type", true);
            }

            if (hasMockAnn && methodExists && isPrivate(meth.getModifiers())) {
                addMarker(node.getName(), "Mocked method should not be private", true);
            }
        }

        return true;
    }

    public IMethodBinding findRealMethod(final MethodDeclaration node, final IMethodBinding meth,
            final ITypeBinding mockedType) {
        IMethodBinding origMethod = null;

        if (mockedType != null)
            origMethod = MockUtil.findRealMethodInType(mockedType, new InvocationFilter(meth), node.getAST());

        return origMethod;
    }

    @Override
    public boolean visit(final AnonymousClassDeclaration node) {
        ITypeBinding binding = node.resolveBinding();

        if (MockUtil.isMockUpType(binding.getSuperclass())) // new MockUp< type >
        {
            ITypeBinding typePar = ASTUtil.getFirstTypeParameter(node);
            ASTNode parent = node.getParent();

            if (parent instanceof ClassInstanceCreation && typePar.isInterface()) // creating interface mock
            {
                ClassInstanceCreation creation = (ClassInstanceCreation) parent;

                visitMockUpCreation(creation);
            }

        }

        return true;
    }

    public void visitMockUpCreation(final ClassInstanceCreation creation) {
        ASTNode gparent = creation.getParent();

        Type typeNode = creation.getType();
        boolean invokesGetInst = false;

        if (gparent instanceof MethodInvocation) // method invocation follows
        {
            MethodInvocation inv = (MethodInvocation) gparent;
            IMethodBinding meth = inv.resolveMethodBinding();

            if (MockUtil.GET_INST.equals(meth.getName())) {
                invokesGetInst = true;
                if (gparent.getParent() instanceof ExpressionStatement) {
                    addMarker(inv.getName(), "Returned mock instance is not being used.", false);
                }
            }
        }

        if (!invokesGetInst) {
            addMarker(typeNode, "Missing call to getMockInstance() on MockUp of interface", false);
        }
    }

    @Override
    public boolean visit(final MethodInvocation node) {
        IMethodBinding meth = node.resolveMethodBinding();

        if (meth == null) {
            return false;
        }

        if (MockUtil.isMockUpType(meth.getDeclaringClass()) && MockUtil.GET_INST.equals(meth.getName())) {
            ITypeBinding returnType = node.resolveTypeBinding();

            if (!returnType.isInterface()) {
                String msg = "getMockInstance() used on mock of class " + returnType.getName()
                        + ". Use on interfaces";
                addMarker(node.getName(), msg, false);
            }
        }

        visitItFieldInvocation(node, meth);

        return true;
    }

    // consider visit(FieldAccess )
    private void visitItFieldInvocation(final MethodInvocation node, final IMethodBinding meth) {
        if (node.getExpression() instanceof SimpleName) {
            SimpleName var = (SimpleName) node.getExpression();
            String varName = var.getIdentifier();

            IBinding exprBinding = var.resolveBinding();

            if ("it".equals(varName) && exprBinding instanceof IVariableBinding) // call on 'it' field
            {
                IVariableBinding varBinding = (IVariableBinding) exprBinding;
                IMethodBinding mockMethod = getSurroundingMockMethod(node);

                if (mockMethod != null && varBinding.isField() && mockMethod.isSubsignature(meth)
                        && varBinding.getDeclaringClass().equals(mockMethod.getDeclaringClass())) {
                    ITypeBinding mockedType = MockUtil.findMockedType(node);

                    if (mockedType.equals(varBinding.getType()) // field type is correct mocked type
                            && !MockUtil.isReentrantMockMethod(mockMethod)) {
                        addMarker(node, "Method calls itself. Set @Mock(reentrant=true) on method '"
                                + mockMethod.getName() + "'", true);
                    }
                }
            }
        }
    }

    private IMethodBinding getSurroundingMockMethod(final MethodInvocation node) {
        MethodDeclaration surroundingMeth = ASTUtil.findAncestor(node, MethodDeclaration.class);
        IMethodBinding mockMethod = null;

        if (surroundingMeth != null) {
            mockMethod = surroundingMeth.resolveBinding();
        }

        if (mockMethod != null && MockUtil.isMockMethod(mockMethod)) {
            return mockMethod;
        } else {
            return null;
        }
    }

    @Override
    public boolean visit(final FieldDeclaration node) {
        //add checks:
        // - 'it' field should have the correct type
        // - it field should not be private
        // - adding other fields to a  mock - warning about shared state?

        //         for(Object fragment: node.fragments())
        //         {
        //            if(fragment instanceof VariableDeclarationFragment)
        //            {
        //               VariableDeclarationFragment varDec = (VariableDeclarationFragment) fragment;
        //
        //               if( "it".equals(varDec.getName().getIdentifier()) )
        //               {
        //                  IVariableBinding ivar = varDec.resolveBinding();
        //
        //                  if( ASTUtil.isMockUpType(ivar.getDeclaringClass().getSuperclass()) )
        //                  {
        //                  }
        //               }
        //            }
        //         }

        return true;
    }

    @Override
    public boolean visit(final TypeDeclaration node) {
        return hasMockitImport;
    }

    @Override
    public boolean visit(final ImportDeclaration node) {
        String importName = node.getName().getFullyQualifiedName();
        hasMockitImport = hasMockitImport || importName.startsWith("mockit.Mock");
        return true;
    }

    private void addMarker(final ASTNode node, final String msg, final boolean isError) {
        try {
            int start = node.getStartPosition();
            int endChar = start + node.getLength();
            int line = cu.getLineNumber(start);
            int col = cu.getColumnNumber(start);
            int id = IProblem.TypeRelated;
            String filePath = icunit.getPath().toOSString();
            String[] args = new String[] {};

            int severity = isError ? ProblemSeverities.Error : ProblemSeverities.Warning;

            CategorizedProblem problem = new DefaultProblem(filePath.toCharArray(), msg, id, args, severity, start,
                    endChar, line, col) {
                @Override
                public String getMarkerType() {
                    return JMockitCompilationParticipant.MARKER;
                }
            };

            probs.add(problem);
        } catch (Exception e) {
            Activator.log(e);
        }
    }

    public CategorizedProblem[] getProblems() {
        return probs.toArray(new CategorizedProblem[] {});
    }

}