/* | |
* Copyright (C) 2012 The Android Open Source Project | |
* | |
* 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.motorolamobility.preflighting.samplechecker.findviewbyid.quickfix; | |
import java.util.List; | |
import java.util.Map; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IMarker; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.runtime.NullProgressMonitor; | |
import org.eclipse.jdt.core.ICompilationUnit; | |
import org.eclipse.jdt.core.JavaCore; | |
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.ASTVisitor; | |
import org.eclipse.jdt.core.dom.Assignment; | |
import org.eclipse.jdt.core.dom.Block; | |
import org.eclipse.jdt.core.dom.CompilationUnit; | |
import org.eclipse.jdt.core.dom.DoStatement; | |
import org.eclipse.jdt.core.dom.EnhancedForStatement; | |
import org.eclipse.jdt.core.dom.Expression; | |
import org.eclipse.jdt.core.dom.ExpressionStatement; | |
import org.eclipse.jdt.core.dom.ForStatement; | |
import org.eclipse.jdt.core.dom.MethodInvocation; | |
import org.eclipse.jdt.core.dom.SimpleName; | |
import org.eclipse.jdt.core.dom.Statement; | |
import org.eclipse.jdt.core.dom.VariableDeclarationFragment; | |
import org.eclipse.jdt.core.dom.VariableDeclarationStatement; | |
import org.eclipse.jdt.core.dom.WhileStatement; | |
import org.eclipse.jdt.ui.JavaUI; | |
import org.eclipse.jface.text.IDocument; | |
import org.eclipse.swt.graphics.Image; | |
import org.eclipse.text.edits.TextEdit; | |
import org.eclipse.ui.IEditorPart; | |
import org.eclipse.ui.IMarkerResolution2; | |
import org.eclipse.ui.PlatformUI; | |
import org.eclipse.ui.texteditor.AbstractTextEditor; | |
import com.motorola.studio.android.common.utilities.EclipseUtils; | |
import com.motorolamobility.preflighting.samplechecker.findviewbyid.quickfix.i18n.MessagesNLS; | |
/** | |
* MarkerResolution responsible for moving the findViewByID call outside the loop. | |
*/ | |
public class FindViewByIdMarkerResolution implements IMarkerResolution2 | |
{ | |
/* | |
* (non-Javadoc) | |
* @see org.eclipse.ui.IMarkerResolution#getLabel() | |
*/ | |
public String getLabel() | |
{ | |
return MessagesNLS.FindViewByIdMarkerResolution_Label; | |
} | |
/* | |
* (non-Javadoc) | |
* @see org.eclipse.ui.IMarkerResolution#run(org.eclipse.core.resources.IMarker) | |
*/ | |
public void run(IMarker marker) | |
{ | |
IResource resource = marker.getResource(); | |
final ICompilationUnit iCompilationUnit = | |
JavaCore.createCompilationUnitFrom((IFile) resource); | |
ASTParser parser = ASTParser.newParser(AST.JLS3); | |
parser.setProject(JavaCore.create(resource.getProject())); | |
parser.setResolveBindings(true); | |
parser.setSource(iCompilationUnit); | |
parser.setStatementsRecovery(true); | |
parser.setBindingsRecovery(true); | |
final CompilationUnit compUnit = (CompilationUnit) parser.createAST(null); | |
try | |
{ | |
final int lineNumber = marker.getAttribute(IMarker.LINE_NUMBER, -1); | |
MethodInvocation invokedMethod = null; | |
//Look for the invokedMethod that shall be moved | |
invokedMethod = getMethodInvocation(compUnit, lineNumber, invokedMethod); | |
IEditorPart editor = openEditor(iCompilationUnit); | |
ASTNode loopNode = getLoopNode(invokedMethod); | |
//Retrieve block parent for the loop statement. | |
Block targetBlock = (Block) loopNode.getParent(); | |
List<Statement> statements = targetBlock.statements(); | |
int i = getLoopStatementIndex(loopNode, statements); | |
//Add the node before the loop. | |
compUnit.recordModifications(); | |
ASTNode invokedMethodStatement = getInvokedStatement(invokedMethod); | |
final VariableDeclarationStatement varDeclarationStatement[] = | |
new VariableDeclarationStatement[1]; | |
//Verify if the invoke statement contains a variable attribution | |
if (invokedMethodStatement instanceof ExpressionStatement) | |
{ | |
ExpressionStatement expressionStatement = | |
(ExpressionStatement) invokedMethodStatement; | |
Expression expression = expressionStatement.getExpression(); | |
if (expression instanceof Assignment) | |
{ | |
Expression leftHandSide = ((Assignment) expression).getLeftHandSide(); | |
if (leftHandSide instanceof SimpleName) //Search for the variable declaration | |
{ | |
SimpleName simpleName = (SimpleName) leftHandSide; | |
final String varName = simpleName.getIdentifier(); | |
loopNode.accept(new ASTVisitor() | |
{ | |
@Override | |
public boolean visit(VariableDeclarationStatement node) //Visit all variable declarations inside the loop looking for the variable which receives the findViewById result | |
{ | |
List<VariableDeclarationFragment> fragments = node.fragments(); | |
for (VariableDeclarationFragment fragment : fragments) | |
{ | |
if (fragment.getName().getIdentifier().equals(varName)) | |
{ | |
varDeclarationStatement[0] = node; | |
break; | |
} | |
} | |
return super.visit(node); | |
} | |
}); | |
} | |
} | |
} | |
//Variable is declared inside the loop, now let's move the variable declaration if needed | |
if (varDeclarationStatement[0] != null) | |
{ | |
ASTNode varDeclarationSubTree = | |
ASTNode.copySubtree(targetBlock.getAST(), varDeclarationStatement[0]); | |
statements.add(i, (Statement) varDeclarationSubTree.getRoot()); | |
//Delete the node inside loop. | |
varDeclarationStatement[0].delete(); | |
i++; | |
} | |
ASTNode copySubtree = ASTNode.copySubtree(targetBlock.getAST(), invokedMethodStatement); | |
statements.add(i, (Statement) copySubtree.getRoot()); | |
//Delete the node inside loop. | |
invokedMethodStatement.delete(); | |
// apply changes to file | |
final Map<?, ?> mapOptions = JavaCore.create(resource.getProject()).getOptions(true); | |
final IDocument document = | |
((AbstractTextEditor) editor).getDocumentProvider().getDocument( | |
editor.getEditorInput()); | |
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() | |
{ | |
public void run() | |
{ | |
try | |
{ | |
TextEdit edit = compUnit.rewrite(document, mapOptions); | |
iCompilationUnit.applyTextEdit(edit, new NullProgressMonitor()); | |
} | |
catch (JavaModelException e) | |
{ | |
EclipseUtils.showErrorDialog( | |
MessagesNLS.FindViewByIdMarkerResolution_Error_Msg_Title, | |
MessagesNLS.FindViewByIdMarkerResolution_Error_Aplying_Changes | |
+ e.getMessage()); | |
} | |
} | |
}); | |
marker.delete(); | |
} | |
catch (Exception e) | |
{ | |
EclipseUtils.showErrorDialog(MessagesNLS.FindViewByIdMarkerResolution_Error_Msg_Title, | |
MessagesNLS.FindViewByIdMarkerResolution_Error_Could_Not_Fix_Code); | |
} | |
} | |
private IEditorPart openEditor(final ICompilationUnit iCompilationUnit) | |
{ | |
IEditorPart editor = null; | |
try | |
{ | |
editor = JavaUI.openInEditor(iCompilationUnit); | |
} | |
catch (Exception e) | |
{ | |
EclipseUtils.showErrorDialog(MessagesNLS.FindViewByIdMarkerResolution_Error_Msg_Title, | |
MessagesNLS.FindViewByIdMarkerResolution_Error_Unable_To_Open_Editor); | |
} | |
return editor; | |
} | |
private MethodInvocation getMethodInvocation(final CompilationUnit compUnit, | |
final int lineNumber, MethodInvocation invokedMethod) | |
{ | |
final MethodInvocation[] tempMethodInvocation = new MethodInvocation[1]; | |
compUnit.accept(new ASTVisitor() | |
{ | |
@Override | |
public boolean visit(MethodInvocation node) | |
{ | |
if (compUnit.getLineNumber(node.getStartPosition()) == lineNumber) | |
{ | |
tempMethodInvocation[0] = node; | |
} | |
return super.visit(node); | |
}; | |
}); | |
if (tempMethodInvocation[0] != null) | |
{ | |
invokedMethod = tempMethodInvocation[0]; | |
} | |
return invokedMethod; | |
} | |
/* | |
* Look for Statement containing the invokedMethod | |
*/ | |
private ASTNode getInvokedStatement(MethodInvocation invokedMethod) | |
{ | |
boolean found = false; | |
ASTNode parent = invokedMethod.getParent(); | |
do | |
{ | |
if ((parent instanceof Statement)) | |
{ | |
found = true; | |
} | |
else | |
{ | |
parent = parent.getParent(); | |
} | |
} | |
while (!found); | |
return parent; | |
} | |
/* | |
* Resturns the index of the loop statement, inside a list of statements | |
*/ | |
private int getLoopStatementIndex(ASTNode loopNode, List<Statement> statements) | |
{ | |
int i = 0; | |
for (i = 0; i < statements.size(); i++) | |
{ | |
Statement statement = statements.get(i); | |
if (statement.equals(loopNode)) | |
{ | |
break; | |
} | |
} | |
return i; | |
} | |
/* | |
* Find the loop node that contains the called method | |
*/ | |
private ASTNode getLoopNode(MethodInvocation invokedMethod) | |
{ | |
boolean found = false; | |
ASTNode parent = invokedMethod.getParent(); | |
do | |
{ | |
parent = parent.getParent(); | |
if ((parent instanceof ForStatement) || (parent instanceof DoStatement) | |
|| (parent instanceof WhileStatement) | |
|| (parent instanceof EnhancedForStatement)) | |
{ | |
found = true; | |
} | |
} | |
while (!found); | |
return parent; | |
} | |
/* | |
* (non-Javadoc) | |
* @see org.eclipse.ui.IMarkerResolution2#getDescription() | |
*/ | |
public String getDescription() | |
{ | |
return MessagesNLS.FindViewByIdMarkerResolution_Description; | |
} | |
public Image getImage() | |
{ | |
// TODO Auto-generated method stub | |
return null; | |
} | |
} |