/*
* 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 methodExists = 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[]{});
}
}