/*
* 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 java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
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.MethodDeclaration;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ITrackedNodePosition;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.formatter.IndentManipulation;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
import org.eclipse.jdt.internal.ui.text.java.JavaTypeCompletionProposal;
import org.eclipse.jdt.ui.CodeGeneration;
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
import org.eclipse.jface.viewers.StyledString;
@SuppressWarnings("restriction")
/**
* Mock method completion proposal. Inserts mock method in code.
*/
public class MockMethodCompletionProposal extends JavaTypeCompletionProposal
implements ICompletionProposalExtension4
{
static final int MAX_RELEVANCE = 100;
static final int METHOD_RELEVANCE = 90;
static final int OBJ_METHOD_RELEVANCE = 80;
private final IMethodBinding method;
private final IJavaProject fJavaProject;
private ICompilationUnit cunit;
private ImportRewriteContext context;
private ImportRewrite importRewrite;
public MockMethodCompletionProposal(final ICompilationUnit cu,
final IMethodBinding meth, final int start, final int length,
final StyledString displayName, final String completionProposal)
throws IllegalArgumentException, JavaModelException
{
super(completionProposal, cu, start, length, null, displayName, METHOD_RELEVANCE );
Assert.isNotNull(meth);
Assert.isNotNull(cu);
method = meth;
cunit = cu;
fJavaProject = cu.getJavaProject();
StringBuffer buffer = new StringBuffer();
buffer.append("@Mock ");
buffer.append(completionProposal);
buffer.append(" { }");
if( Object.class.getName().equals( method.getDeclaringClass().getQualifiedName() ) ) // from Object
{
setImage(JavaPluginImages.get(JavaPluginImages.IMG_MISC_PUBLIC));
setRelevance(OBJ_METHOD_RELEVANCE);
}
else
{
setImage(JavaPluginImages.get(JavaPluginImages.IMG_MISC_DEFAULT));
}
setReplacementString(buffer.toString());
}
@Override
public final CharSequence getPrefixCompletionText(final IDocument document, final int completionOffset)
{
return method.getName();
}
@Override
protected final boolean updateReplacementString(final IDocument document, final char trigger, final int offset,
final ImportRewrite importRw) throws CoreException, BadLocationException
{
try
{
Document recoveredDocument = new Document();
CompilationUnit unit = getRecoveredAST(document, offset, recoveredDocument);
initContext(offset, importRw, unit);
ASTNode node = NodeFinder.perform(unit, offset, 1);
AST ast = unit.getAST();
ASTRewrite rewrite = ASTRewrite.create(ast);
CodeGenerationSettings settings = getCodeGenSettings();
MethodDeclaration stub = createMockMethodStub(ast, rewrite, settings);
String methodDeclarationText = generateMethodDeclaration(document, recoveredDocument,
node, rewrite, settings, stub);
setReplacementString(methodDeclarationText);
}
catch (Exception exception)
{
Activator.log(exception);
}
return true;
}
private String generateMethodDeclaration(final IDocument document, final Document recoveredDocument,
final ASTNode node, final ASTRewrite rewrite, final CodeGenerationSettings settings,
final MethodDeclaration stub) throws BadLocationException
{
ChildListPropertyDescriptor descriptor = getPropDescriptor(node);
ListRewrite rewriter = rewrite.getListRewrite(node, descriptor);
rewriter.insertFirst(stub, null);
ITrackedNodePosition position = rewrite.track(stub);
rewrite.rewriteAST(recoveredDocument, fJavaProject.getOptions(true)).apply(recoveredDocument);
String generatedCode = recoveredDocument.get(position.getStartPosition(), position.getLength());
int generatedIndent = IndentManipulation.measureIndentUnits(
getIndentAt(recoveredDocument, position.getStartPosition(), settings), settings.tabWidth,
settings.indentWidth);
String indent = getIndentAt(document, getReplacementOffset(), settings);
String methodDeclarationText = IndentManipulation.changeIndent(generatedCode, generatedIndent,
settings.tabWidth, settings.indentWidth, indent,
TextUtilities.getDefaultLineDelimiter(document));
return methodDeclarationText;
}
private MethodDeclaration createMockMethodStub(final AST ast, final ASTRewrite rewrite,
final CodeGenerationSettings settings) throws CoreException, JavaModelException
{
ITypeBinding declaringType = method.getDeclaringClass();
MethodDeclaration stub;
IBinding contextBinding = declaringType;
stub = StubUtility2.createImplementationStub(fCompilationUnit, rewrite, importRewrite, context, method,
declaringType, settings, false, contextBinding);
// stub = StubUtility2.createImplementationStub(fCompilationUnit, rewrite,
// importRewrite, context, method, declaringType, settings, false);
if( !Object.class.getName().equals( method.getDeclaringClass().getQualifiedName() ) )
{
stub.modifiers().clear();
}
ASTUtil.addAnnotation("Mock", fJavaProject, rewrite, stub, method);
importRewrite.addImport(MockUtil.MOCK, context);
if( method.isConstructor() )
{
stub.setName( ast.newSimpleName(MockUtil.CTOR) );
stub.getBody().statements().clear();
}
else
{
setReturnStatement(stub, method, declaringType, ast, rewrite);
}
if( "void".equals(method.getReturnType().getName()) )
{
stub.getBody().statements().clear();
}
return stub;
}
private static ImportRewriteContext createImportRewriteContext()
{
ImportRewriteContext importContext;
importContext = new ImportRewriteContext()
{ // forces that all imports are fully qualified
@Override
public int findInContext(final String qualifier, final String name, final int kind)
{
return RES_NAME_CONFLICT;
}
};
return importContext;
}
@SuppressWarnings("unchecked")
private void setReturnStatement(final MethodDeclaration stub, final IMethodBinding methodToOverride,
final ITypeBinding declaringType, final AST ast, final ASTRewrite rewrite)
throws JavaModelException, CoreException
{
Expression expression= ASTNodeFactory.newDefaultExpression(ast,
stub.getReturnType2(), stub.getExtraDimensions());
if (expression != null)
{
ReturnStatement returnStatement= ast.newReturnStatement();
returnStatement.setExpression(expression);
String delimiter= cunit.findRecommendedLineSeparator();
Map<String, String> options= fJavaProject.getOptions(true);
String bodyStatement= ASTNodes.asFormattedString(returnStatement, 0, delimiter, options);
String placeHolder= CodeGeneration.getMethodBodyContent(cunit,
declaringType.getName(), methodToOverride.getName(), false, bodyStatement, delimiter);
if (placeHolder != null)
{
ASTNode todoNode = rewrite.createStringPlaceholder(placeHolder, ASTNode.RETURN_STATEMENT);
stub.getBody().statements().clear();
stub.getBody().statements().add(todoNode);
}
}
}
/*
* @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension4#isAutoInsertable()
*/
@Override
public final boolean isAutoInsertable()
{
return false;
}
private void initContext(final int offset, final ImportRewrite importRw, final CompilationUnit unit)
{
importRewrite = importRw;
if (importRewrite != null)
{
context = new ContextSensitiveImportRewriteContext(unit, offset, importRewrite);
}
else
{
importRewrite = StubUtility.createImportRewrite(unit, true); // create a dummy import rewriter to have one
context = createImportRewriteContext();
}
}
private CodeGenerationSettings getCodeGenSettings()
{
CodeGenerationSettings settings = JavaPreferencesSettings.getCodeGenerationSettings(fJavaProject);
settings.overrideAnnotation = false;
settings.createComments = false;
return settings;
}
private ChildListPropertyDescriptor getPropDescriptor(final ASTNode node)
{
ChildListPropertyDescriptor descriptor = TypeDeclaration.BODY_DECLARATIONS_PROPERTY;
if (node instanceof AnonymousClassDeclaration)
{
descriptor = AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY;
}
return descriptor;
}
private static String getIndentAt(final IDocument document, final int offset, final CodeGenerationSettings settings)
{
try
{
IRegion region = document.getLineInformationOfOffset(offset);
return IndentManipulation.extractIndentString(document.get(region.getOffset(), region.getLength()),
settings.tabWidth, settings.indentWidth);
}
catch (BadLocationException e)
{
return "";
}
}
private CompilationUnit getRecoveredAST(final IDocument document, final int offset,
final Document recoveredDocument)
{
CompilationUnit ast = SharedASTProvider.getAST(fCompilationUnit, SharedASTProvider.WAIT_ACTIVE_ONLY, null);
if (ast != null)
{
recoveredDocument.set(document.get());
return ast;
}
char[] content = document.get().toCharArray();
// clear prefix to avoid compile errors
int index = offset - 1;
while (index >= 0 && Character.isJavaIdentifierPart(content[index]))
{
content[index] = ' ';
index--;
}
recoveredDocument.set(new String(content));
final ASTParser parser = ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
parser.setResolveBindings(true);
parser.setStatementsRecovery(true);
parser.setSource(content);
parser.setUnitName(fCompilationUnit.getElementName());
parser.setProject(fCompilationUnit.getJavaProject());
return (CompilationUnit) parser.createAST(new NullProgressMonitor());
}
}