/* * 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.checkers.logic; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.motorolamobility.preflighting.checkers.i18n.CheckerNLS; import com.motorolamobility.preflighting.core.applicationdata.ApplicationData; import com.motorolamobility.preflighting.core.applicationdata.SourceFolderElement; import com.motorolamobility.preflighting.core.checker.condition.CanExecuteConditionStatus; import com.motorolamobility.preflighting.core.checker.condition.Condition; import com.motorolamobility.preflighting.core.checker.condition.ICondition; import com.motorolamobility.preflighting.core.devicespecification.DeviceSpecification; import com.motorolamobility.preflighting.core.exception.PreflightingCheckerException; import com.motorolamobility.preflighting.core.internal.cond.utils.ConditionUtils; import com.motorolamobility.preflighting.core.logging.PreflightingLogger; import com.motorolamobility.preflighting.core.source.model.Instruction; import com.motorolamobility.preflighting.core.source.model.Invoke; import com.motorolamobility.preflighting.core.source.model.Method; import com.motorolamobility.preflighting.core.source.model.SourceFileElement; import com.motorolamobility.preflighting.core.source.model.Variable; import com.motorolamobility.preflighting.core.utils.CheckerUtils; import com.motorolamobility.preflighting.core.validation.ValidationManagerConfiguration; import com.motorolamobility.preflighting.core.validation.ValidationResult; import com.motorolamobility.preflighting.core.validation.ValidationResultData; /** * This condition verifies if all opened db cursors are closed within a method */ public class OpenedCursorsCondition extends Condition implements ICondition { private static final String DATABASE_CURSOR_QUALIFIED_NAME = "android.database.Cursor"; //$NON-NLS-1$ private boolean isProject; /* * Exclude managed cursors */ private static final String ACTIVITY_QUALIFIED_NAME = "android.app.Activity"; private static final String START_MANAGING_CURSOR_METHOD_NAME = ".startManagingCursor"; private static final String MANAGED_QUERY_METHOD_NAME = ".managedQuery"; // close method private static final String CLOSE_METHOD_NAME = ".close"; //$NON-NLS-1$ @Override public CanExecuteConditionStatus canExecute(ApplicationData data, List<DeviceSpecification> deviceSpecs) throws PreflightingCheckerException { return CheckerUtils.isJavaModelComplete(data, getId()); } @Override public void execute(ApplicationData data, List<DeviceSpecification> deviceSpecs, ValidationManagerConfiguration valManagerConfig, ValidationResult results) throws PreflightingCheckerException { this.isProject = data.isProject(); List<SourceFolderElement> sourceFolderElements = data.getJavaModel(); for (SourceFolderElement sourceFolder : sourceFolderElements) { List<SourceFileElement> sourceFiles = sourceFolder.getSourceFileElements(); for (SourceFileElement sourceFile : sourceFiles) { //Look on virtual methods List<Method> methods = sourceFile.getVirtualMethods(); analizeMethods(valManagerConfig, results, sourceFile, methods); //Look on direct methods methods = sourceFile.getDirectMethods(); analizeMethods(valManagerConfig, results, sourceFile, methods); } } } private void analizeMethods(ValidationManagerConfiguration valManagerConfig, ValidationResult results, SourceFileElement sourceFile, List<Method> methods) { for (Method method : methods) { List<Instruction> instructions = method.getInstructions(); //Get opened cursors map, variable x num. times it was assigned a new cursor without being closed Map<Variable, Integer> openedCursors = getOpenedCursors(method, sourceFile, instructions); //For each opened cursor fill a validation result for (Variable variable : openedCursors.keySet()) { int openedCursor = openedCursors.get(variable); if (openedCursor > 0) { addValidationResult(results, valManagerConfig, sourceFile, method, variable, openedCursors); } } } } private Map<Variable, Integer> getOpenedCursors(Method method, SourceFileElement sourceFile, List<Instruction> instructions) { Map<Variable, Integer> openedCursors = new HashMap<Variable, Integer>(); for (Instruction instruction : instructions) { if (instruction instanceof Invoke) { Invoke calledMethod = (Invoke) instruction; if ((calledMethod.getReturnType() != null) && (calledMethod.getQualifiedName() != null)) { /* * NOTE: There is a difference between the Java Model for Projects and APKs * * For Projects, when an inherit method is called, it's qualified name contains the * class which actually contains the method. * * For APKs, this do not occur, and the qualified name contains the name of the * class itself. * * Ex: calling android.app.Activity.startManagingCursor * - For Projects: qualified name = android.app.Activity.startManagingCursor * - For APKs: qualified name = <the user activity>.startManagingCursor */ /* * Opening cursors */ if ((calledMethod.getReturnType() != null) && (calledMethod.getReturnType().equals(DATABASE_CURSOR_QUALIFIED_NAME)) && (!calledMethod.getQualifiedName().endsWith( ((isProject) ? ACTIVITY_QUALIFIED_NAME : sourceFile .getClassFullPath()) + MANAGED_QUERY_METHOD_NAME))) { // Opened cursor found, increment counter Variable variable = getAssignedVariable(method, calledMethod); Integer counter = null; if (variable != null) { if (openedCursors.containsKey(variable)) { counter = openedCursors.get(variable); counter++; } else { counter = 1; } openedCursors.put(variable, counter); } } /* * Closing cursors */ String objectName = null; //close() found if (calledMethod.getQualifiedName().equals( DATABASE_CURSOR_QUALIFIED_NAME + CLOSE_METHOD_NAME)) { objectName = calledMethod.getObjectName(); } // startManagingCursos(cursor) found - disregard this cursor else if (calledMethod.getQualifiedName().endsWith( ((isProject) ? ACTIVITY_QUALIFIED_NAME : sourceFile.getClassFullPath()) + START_MANAGING_CURSOR_METHOD_NAME)) { List<String> paramNames = calledMethod.getParameterNames(); if ((paramNames != null) && (paramNames.size() > 0)) { objectName = paramNames.get(0); } } // decrement the counter if (objectName != null) { Variable key = getKey(openedCursors, objectName); Integer counter = null; if (openedCursors.containsKey(key)) { counter = openedCursors.get(key); counter--; openedCursors.put(key, counter); } else { //Don't do anything. for some reason a close was called on a non local variable. //In the future add support for parameters and fields } } } else { PreflightingLogger .error("Could not retrieve qualified name or return type for method: " + calledMethod); } } } return openedCursors; } /* * Search within openedCursors keySet for a variable with name that is equal to objectName */ private Variable getKey(Map<Variable, Integer> openedCursors, String objectName) { Variable key = null; Iterator<Variable> it = openedCursors.keySet().iterator(); while ((key == null) && it.hasNext()) { Variable variable = it.next(); if (variable.getName().equals(objectName)) { key = variable; } } return key; } /* * Retrieve the Variable assigned with the return of calledMethod, if any. */ private Variable getAssignedVariable(Method method, Invoke calledMethod) { String assignedVariable = calledMethod.getAssignedVariable(); List<Variable> variables = method.getVariables(); Variable variable = null; Iterator<Variable> it = variables.iterator(); while ((variable == null) && it.hasNext()) { Variable listVar = it.next(); if (listVar.getName().equals(assignedVariable)) { variable = listVar; } } return variable; } private void addValidationResult(ValidationResult results, ValidationManagerConfiguration valManagerConfig, SourceFileElement sourceFile, Method method, Variable variable, Map<Variable, Integer> openedCursors) { ValidationResultData resultData = new ValidationResultData(); resultData.setConditionID(getId()); resultData.addFileToIssueLines(sourceFile.getFile(), Arrays.asList(new Integer[] { variable.getLineNumber() })); resultData.setIssueDescription(CheckerNLS.bind( CheckerNLS.OpenedCursorsCondition_Result_Message, new String[] { variable.getName(), sourceFile.getClassFullPath() + "." //$NON-NLS-1$ + method.getMethodName(), openedCursors.get(variable).toString() })); resultData.setQuickFixSuggestion(CheckerNLS.OpenedCursorsCondition_Result_QuickFix); resultData.setSeverity(getSeverityLevel()); resultData.setInfoURL(ConditionUtils.getDescriptionLink(getChecker().getId(), getId(), valManagerConfig)); results.addValidationResult(resultData); } }