/*
* 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.implementation;
import java.io.File;
import java.util.ArrayList;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
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.ForStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.WhileStatement;
import com.motorolamobility.preflighting.core.logging.PreflightingLogger;
import com.motorolamobility.preflighting.core.utils.CheckerUtils;
import com.motorolamobility.preflighting.core.validation.ValidationResult;
import com.motorolamobility.preflighting.core.validation.ValidationResultData;
import com.motorolamobility.preflighting.core.validation.ValidationResultData.SEVERITY;
import com.motorolamobility.preflighting.samplechecker.findviewbyid.i18n.Messages;
/**
* Visitor specialized to identify <code>findViewById</code> statements inside loops.
*/
public class FindViewByIdVisitor extends ASTVisitor
{
private static final String R_CONSTANT = "R."; //$NON-NLS-1$
private static final String FIND_VIEW_BY_ID_METHOD_BINDING =
"public android.view.View findViewById(int)"; //$NON-NLS-1$
private final ValidationResult results;
private final CompilationUnit compilationUnit;
private final String id;
private final SEVERITY severityLevel;
private String markerType;
/**
* Construct a new FindViewByIdVisitor with the given parameters.
*
* @param id the condition id
* @param severityLevel the condition default severity level
* @param markerType object to keep the checker results
* @param results
* @param valManagerConfig manager responsible to format output of App Validator
* @param compilationUnit object representing the source code to be analyzed
*/
public FindViewByIdVisitor(String id, SEVERITY severityLevel, String markerType,
ValidationResult results, CompilationUnit compilationUnit)
{
this.id = id;
this.severityLevel = severityLevel;
this.markerType = markerType;
this.results = results;
this.compilationUnit = compilationUnit;
}
/**
* Visit method invocations to find invocation of <code>findViewById</code>.
*
* @param invoked method that is being called and will be analyzed to check if it is a <code>findViewById</code> call.
*/
@Override
public boolean visit(MethodInvocation invoked)
{
//find the signature of the method that is being called
IMethodBinding methodBinding = invoked.resolveMethodBinding();
if (methodBinding != null)
{
if (methodBinding.toString().trim().equalsIgnoreCase(FIND_VIEW_BY_ID_METHOD_BINDING))
{
//according to the signature, findViewById was called
ASTNode parentNode = invoked.getParent();
Object firstElement = invoked.arguments().get(0);
if (firstElement != null && firstElement.toString() != null
&& firstElement.toString().startsWith(R_CONSTANT))
{
//argument has a constant R. (indicating access that could be possibly done outside the loop)
if (hasLoopStatementAsParent(parentNode))
{
//print in the console (if DEBUG level set for verbosity of App Validator output)
PreflightingLogger
.debug("Found findViewById invocation inside loop statement");
//call is inside a loop statement - raise issue
ValidationResultData validationResult = createResult(invoked);
results.addValidationResult(validationResult);
}
}
}
}
return super.visit(invoked);
}
/**
* Recursively check if <code>node</code> has a loop statement as parent.
* @param node to check if parent is a loop statement
* @return <code>true</code> if finds (<code>for, extended for, while, do-while</code>) as parent of <code>node</code>, <code>false</code> otherwise
*/
private boolean hasLoopStatementAsParent(ASTNode node)
{
if (node == null)
{
return false;
}
else
{
ASTNode parentNode = node.getParent();
if (parentNode == null || parentNode instanceof MethodDeclaration)
{
//base case of recursion: reached top level (method declaration or class declaration) without finding a loop statement
return false;
}
else if (isLoopStatement(parentNode))
{
//base case of recursion: reached loop statement
return true;
}
else
{
//continue search : go to the parent node
return hasLoopStatementAsParent(parentNode);
}
}
}
/**
* Check if the {@link ASTNode} is a loop block (<code>for, extended for, while, do-while</code>).
*
* @param statement node to verify
* @return <code>true</code> if (<code>for, extended for, while, do-while</code>), <code>false</code> otherwise
*
* @see org.eclipse.jdt.core.dom.ForStatement
* @see org.eclipse.jdt.core.dom.DoStatement
* @see org.eclipse.jdt.core.dom.WhileStatement
* @see org.eclipse.jdt.core.dom.EnhancedForStatement
*/
private boolean isLoopStatement(ASTNode statement)
{
return statement instanceof ForStatement || statement instanceof DoStatement
|| statement instanceof WhileStatement || statement instanceof EnhancedForStatement;
}
/**
* Create the App Validator issues found for the checker.
*
* @param invoked method where the problem occurs
* @return data containing the issue
*/
private ValidationResultData createResult(MethodInvocation invoked)
{
ValidationResultData resultData = new ValidationResultData();
//set the condition related to the problem
resultData.setConditionID(id);
resultData.setMarkerType(markerType);
//set the lines where the problem occurred
ArrayList<Integer> lines = new ArrayList<Integer>();
int issuedLine = compilationUnit.getLineNumber(invoked.getStartPosition());
if (issuedLine != -1)
{
lines.add(issuedLine);
}
//set the source file associated with the issue
File javaFile = (File) compilationUnit.getProperty(CheckerUtils.JAVA_FILE_PROPERTY);
resultData.addFileToIssueLines(javaFile, lines);
//set description, quick fix, and severity level
resultData.setIssueDescription(Messages.FindViewByIdInsideLoopCondition_IssueDescription);
resultData
.setQuickFixSuggestion(Messages.FindViewByIdInsideLoopCondition_QuickFixSuggestion);
resultData.setSeverity(severityLevel);
//set the URL with the help associated with developer page regarding this checker
resultData
.setInfoURL("http://developer.motorola.com/docstools/library/motodev-app-validator/#unnecessaryFindViewById-unnecessaryFindViewByIdInsideLoops");
return resultData;
}
}