blob: d7a45a646600365ee6437fc941eb3ccec7ecf642 [file] [log] [blame]
/*
* 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.buildingblocksdeclaration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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.applicationdata.XMLElement;
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.source.model.SourceFileElement;
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 whether a building block inherits
* from the class it is supposed to.
*
*/
public class BuildingBlocksInheritanceCondition extends Condition implements ICondition
{
/**
* Enumerator holds all building block types.
*/
private enum BuildingBlockType
{
ACTIVITY, SERVICE, CONTENT_PROVIDER, BROADCAST_RECEIVER
}
private static final String ANDROID_NAME_NODE_ATTRIBUTE = "android:name"; //$NON-NLS-1$
private static final String ACTIVITY_NODE_NAME = "activity"; //$NON-NLS-1$
private static final String PROVIDER_NODE_NAME = "provider"; //$NON-NLS-1$
private static final String RECEIVER_NODE_NAME = "receiver"; //$NON-NLS-1$
private static final String SERVICE_NODE_NAME = "service"; //$NON-NLS-1$
private static final String MANIFEST_NODE_NAME = "manifest"; //$NON-NLS-1$
private static final String PACKAGE_NAME_NODE_ATTRIBUTE = "package"; //$NON-NLS-1$
private static final String ACTIVITY_CLASS_NAME = "android.app.Activity"; //$NON-NLS-1$
private static final String CONTENT_PROVIDER_CLASS_NAME = "android.content.ContentProvider"; //$NON-NLS-1$
private static final String BROADCAST_RECEIVER_CLASS_NAME = "android.content.BroadcastReceiver"; //$NON-NLS-1$
private static final String SERVICE_CLASS_NAME = "android.app.Service"; //$NON-NLS-1$
private static String DEFAULT_PACKAGE_REPRESENTATION_REGULAR_EXPRESSION = "\\."; //$NON-NLS-1$
private static String DEFAULT_PACKAGE_REPRESENTATION = "."; //$NON-NLS-1$
private static char PACKAGE_SEPARATOR = '.'; //$NON-NLS-1$
private static char PATH_SEPARATOR = '/'; //$NON-NLS-1$
private static String COLUMN_STRING = ";"; //$NON-NLS-1$
private static int ZERO_INDEX = 0;
private static int FIRST_INDEX = 1;
private static String[] KNOWN_ACITIVITES_IMPLEMENTATIONS = new String[]
{
"android.app.Activity", "android.accounts.AccountAuthenticatorActivity",
"android.app.ActivityGroup", "android.app.AliasActivity",
"android.app.ExpandableListActivity", "android.app.ListActivity",
"android.app.NativeActivity", "android.app.LauncherActivity",
"android.preference.PreferenceActivity", "android.app.TabActivity"
};
private static String[] KNOWN_CONTENT_PROVIDERS_IMPLEMENTATIONS = new String[]
{
"android.content.ContentProvider", "android.test.mock.MockContentProvider",
"android.content.SearchRecentSuggestionsProvider"
};
private static String[] KNOWN_BROADCAST_RECEIVERS_IMPLEMENTATIONS = new String[]
{
"android.content.BroadcastReceiver", "android.appwidget.AppWidgetProvider",
"android.app.admin.DeviceAdminReceiver"
};
private static String[] KNOWN_SERVICES_IMPLEMENTATIONS = new String[]
{
"android.app.Service", "android.inputmethodservice.AbstractInputMethodService",
"android.accessibilityservice.AccessibilityService", "android.app.IntentService",
"android.speech.RecognitionService", "android.widget.RemoteViewsService",
"android.service.wallpaper.WallpaperService",
"android.inputmethodservice.InputMethodService"
};
/*
* (non-Javadoc)
* @see com.motorolamobility.preflighting.core.checker.condition.Condition#canExecute(com.motorolamobility.preflighting.core.applicationdata.ApplicationData, java.util.List)
*/
@Override
public CanExecuteConditionStatus canExecute(ApplicationData data,
List<DeviceSpecification> deviceSpecs) throws PreflightingCheckerException
{
return (CanExecuteConditionStatus) CheckerUtils
.isAndroidManifestFileExistent(data, getId());
}
/*
* (non-Javadoc)
* @see com.motorolamobility.preflighting.core.checker.condition.Condition#execute(com.motorolamobility.preflighting.core.applicationdata.ApplicationData, java.util.List, com.motorolamobility.preflighting.core.devicespecification.PlatformRules, com.motorolamobility.preflighting.core.validation.ValidationManagerConfiguration)
*/
@Override
public void execute(ApplicationData data, List<DeviceSpecification> deviceSpecs,
ValidationManagerConfiguration valManagerConfig, ValidationResult results)
throws PreflightingCheckerException
{
XMLElement document = data.getManifestElement();
Document manifestDoc = document.getDocument();
List<SourceFolderElement> folders = data.getJavaModel();
// get all missdeclared classes from all building blocks
List<String> missDeclaredActivities =
getMissDeclaredBuildingBlocks(manifestDoc, folders, BuildingBlockType.ACTIVITY);
List<String> missDeclaredContentProviders =
getMissDeclaredBuildingBlocks(manifestDoc, folders,
BuildingBlockType.CONTENT_PROVIDER);
List<String> missDeclaredBroadcastReceivers =
getMissDeclaredBuildingBlocks(manifestDoc, folders,
BuildingBlockType.BROADCAST_RECEIVER);
List<String> missDelcaredServices =
getMissDeclaredBuildingBlocks(manifestDoc, folders, BuildingBlockType.SERVICE);
// having all missdeclared building blocks, add their appropriated error messages
addValidationResults(results, missDeclaredActivities, BuildingBlockType.ACTIVITY,
valManagerConfig);
addValidationResults(results, missDeclaredBroadcastReceivers,
BuildingBlockType.BROADCAST_RECEIVER, valManagerConfig);
addValidationResults(results, missDeclaredContentProviders,
BuildingBlockType.CONTENT_PROVIDER, valManagerConfig);
addValidationResults(results, missDelcaredServices, BuildingBlockType.SERVICE,
valManagerConfig);
}
/**
* Get the default package name from the AndroidManifest.xml file.
*
* @param manifestDoc {@link Document} representing the AndroidManifest.xml file.
*
* @return Returns the default package name.
*/
private String getDefaultPackageName(Document manifestDoc)
{
NodeList nodeList = manifestDoc.getElementsByTagName(MANIFEST_NODE_NAME);
return nodeList.item(ZERO_INDEX).getAttributes().getNamedItem(PACKAGE_NAME_NODE_ATTRIBUTE)
.getNodeValue();
}
/**
* Add validation results to the results list.
*
* @param results {@link ValidationResult} list where
* the {@link ValidationResultData} is added.
* @param missDelcaredBuildingBlocks The list of missdeclared building blocks
* to be managed.
* @param buildingBlockType {@link BuildingBlockType}.
* @param valManagerConfig Validation manager result
*/
private void addValidationResults(ValidationResult results,
List<String> missDelcaredBuildingBlocks, BuildingBlockType buildingBlockType,
ValidationManagerConfiguration valManagerConfig)
{
String buildingBlockClassName = null;
String buildingBockText = null;
switch (buildingBlockType)
{
case ACTIVITY:
buildingBlockClassName = ACTIVITY_CLASS_NAME;
buildingBockText = CheckerNLS.BuildingBlocksInheritanceCondition_Activity;
break;
case BROADCAST_RECEIVER:
buildingBlockClassName = BROADCAST_RECEIVER_CLASS_NAME;
buildingBockText = CheckerNLS.BuildingBlocksInheritanceCondition_BroadcastReceiver;
break;
case CONTENT_PROVIDER:
buildingBlockClassName = CONTENT_PROVIDER_CLASS_NAME;
buildingBockText = CheckerNLS.BuildingBlocksInheritanceCondition_ContentProvider;
break;
case SERVICE:
buildingBlockClassName = SERVICE_CLASS_NAME;
buildingBockText = CheckerNLS.BuildingBlocksInheritanceCondition_Service;
break;
}
if ((missDelcaredBuildingBlocks != null) && (missDelcaredBuildingBlocks.size() > 0))
{
ValidationResultData validationResult = null;
for (String missDelcaredBuildingBlock : missDelcaredBuildingBlocks)
{
validationResult = new ValidationResultData();
validationResult.setConditionID(getId());
validationResult
.setIssueDescription(CheckerNLS
.bind(CheckerNLS.BuildingBlockDeclarationCondition_TheClassXShouldExtendBuildingBlockY,
new String[]
{
missDelcaredBuildingBlock, buildingBockText
}));
validationResult
.setQuickFixSuggestion(CheckerNLS
.bind(CheckerNLS.BuildingBlocksInheritanceCondition_TheBuildingBlockXShouldExtendClassY,
new String[]
{
missDelcaredBuildingBlock, buildingBlockClassName
}));
validationResult.setInfoURL(ConditionUtils.getDescriptionLink(getChecker().getId(),
getId(), valManagerConfig));
validationResult.setSeverity(getSeverityLevel());
results.addValidationResult(validationResult);
}
}
}
/**
* Retrieve the list of missdeclared building blocks of
* a certain {@link BuildingBlockType}.
*
* @param manifestDoc AndroidManifest.xml {@link Document} representation.
* @param folders The {@link SourceFolderElement} list which represents
* the Android Project.
* @param buildingBlockType The building block type to be analyzed.
*
* @return Returns the list of missdeclared building blocks for a certain
* {@link BuildingBlockType}.
*/
private List<String> getMissDeclaredBuildingBlocks(Document manifestDoc,
List<SourceFolderElement> folders, BuildingBlockType buildingBlockType)
{
String selectedNodeName = null;
String[] selectedBuildingBlockClassNameList = null;
// determined the nodes and classes to search accoding to the building block
switch (buildingBlockType)
{
case ACTIVITY:
selectedNodeName = ACTIVITY_NODE_NAME;
selectedBuildingBlockClassNameList = KNOWN_ACITIVITES_IMPLEMENTATIONS;
break;
case CONTENT_PROVIDER:
selectedNodeName = PROVIDER_NODE_NAME;
selectedBuildingBlockClassNameList = KNOWN_CONTENT_PROVIDERS_IMPLEMENTATIONS;
break;
case BROADCAST_RECEIVER:
selectedNodeName = RECEIVER_NODE_NAME;
selectedBuildingBlockClassNameList = KNOWN_BROADCAST_RECEIVERS_IMPLEMENTATIONS;
break;
case SERVICE:
selectedNodeName = SERVICE_NODE_NAME;
selectedBuildingBlockClassNameList = KNOWN_SERVICES_IMPLEMENTATIONS;
break;
}
List<String> missDeclaredBuldingBlocks = new ArrayList<String>();
Node buildingBlockNode = null;
Node nodeName = null;
String buildingBlockName = null;
String sourceFileName = null;
String superClassName = null;
List<SourceFileElement> files = null;
// get all building block nodes
NodeList applicationNodes = manifestDoc.getElementsByTagName(selectedNodeName);
// For each building block node, check whether its superclass is
// supposed parent
if ((applicationNodes != null) && (applicationNodes.getLength() > 0))
{
for (int index = 0; index < applicationNodes.getLength(); index++)
{
// get the building block name
buildingBlockNode = applicationNodes.item(index);
nodeName =
buildingBlockNode.getAttributes().getNamedItem(ANDROID_NAME_NODE_ATTRIBUTE);
if (nodeName != null)
{
buildingBlockName = nodeName.getNodeValue();
// get the full package name of the building block
buildingBlockName = adjustFullPackageName(buildingBlockName, manifestDoc);
// Find the equivalent source of the building block and check
// its superclass.
for (SourceFolderElement folder : folders)
{
files = folder.getSourceFileElements();
if ((files != null) && (files.size() > 0))
{
for (SourceFileElement file : files)
{
// for executing the checker, for now it cannot be an inner class
// TODO : this verification can be removed after the correct class name is retrieved
if (!file.isInnerClass())
{
sourceFileName = file.getClassFullPath();
sourceFileName = getFullPackagePathFromFullPath(sourceFileName);
if ((sourceFileName != null)
&& sourceFileName.equals(buildingBlockName))
{
superClassName = file.getSuperclassName();
superClassName =
getFullPackagePathFromFullPath(superClassName);
if (!Arrays.asList(selectedBuildingBlockClassNameList)
.contains(superClassName))
{
// The superclass is not the expected one!
missDeclaredBuldingBlocks.add(sourceFileName);
}
}
}
}
}
}
}
}
}
// retrieve the list of miss declared building blocks
return missDeclaredBuldingBlocks;
}
/**
* Given the full path to a Java file, its full packaged-name representation
* is returned.
*
* @param fullPath The full path of a Java file.
*
* @return Returns the full package-name of a Java file.
*/
public String getFullPackagePathFromFullPath(String fullPath)
{
String fullPackageAndClassName = null;
fullPackageAndClassName = fullPath.replace(PATH_SEPARATOR, PACKAGE_SEPARATOR);
if (fullPackageAndClassName.endsWith(COLUMN_STRING))
{
fullPackageAndClassName = fullPackageAndClassName.split(COLUMN_STRING)[ZERO_INDEX];
}
return fullPackageAndClassName;
}
/**
* Set the full package name for the building block.
*
* @param buildingBlockName Building block name.
* @param manifestDoc AndroidManifest.xml {@link Document}.
*
* @return Returns the building block with its full package name.
*/
private String adjustFullPackageName(String buildingBlockName, Document manifestDoc)
{
String defaultPackageName = getDefaultPackageName(manifestDoc);
if (!buildingBlockName.contains(defaultPackageName))
{
if (buildingBlockName.contains(DEFAULT_PACKAGE_REPRESENTATION))
{
buildingBlockName =
buildingBlockName.split(DEFAULT_PACKAGE_REPRESENTATION_REGULAR_EXPRESSION)[FIRST_INDEX];
}
buildingBlockName = defaultPackageName + PACKAGE_SEPARATOR + buildingBlockName;
}
return buildingBlockName;
}
}