/* | |
* 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; | |
} | |
} |