/* * 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.androidlabel.implementation; import java.util.ArrayList; import java.util.List; import java.util.Locale; import org.eclipse.core.runtime.IStatus; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.motorolamobility.preflighting.core.applicationdata.ApplicationData; import com.motorolamobility.preflighting.core.applicationdata.Element; import com.motorolamobility.preflighting.core.applicationdata.Element.Type; import com.motorolamobility.preflighting.core.applicationdata.ElementUtils; import com.motorolamobility.preflighting.core.applicationdata.ResourcesFolderElement; import com.motorolamobility.preflighting.core.applicationdata.StringsElement; 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.logging.PreflightingLogger; 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; import com.motorolamobility.preflighting.samplechecker.androidlabel.AndroidLabelActivator; import com.motorolamobility.preflighting.samplechecker.androidlabel.i18n.AndroidLabelCheckerNLS; /** * This Condition verifies whether a given text is a substring of the Android Application Label. * <br><br> * For this condition, a text is entered using the parameter labelText. In order to * have no warnings reported, all resources which the Android Application * Label points out, must have this parameter as a part of its name. */ public class CorrectTextInLabelCondition extends Condition implements ICondition { /** * Represents the AndroidManifest.xml application node. */ private static final String MANIFEST_TAG_APPLICATION = "application"; //$NON-NLS-1$ /** * Represents the AndroidManifest.xml label property name. */ private static final String MANIFEST_TAG_LABEL = "android:label"; //$NON-NLS-1$ /** * Represents the prefix for String resources on the AndroidManifest.xml */ private static final String ANDROID_STRING_IDENTIFIER = "@string/"; //$NON-NLS-1$ private String parameterText; /** * Executes the {@link AndroidLabelChecker} validations, which are: * <ul> * <li>The entered label must be contained in the default resource.</li> * <li>The entered label must be contained in all alternative resources.</li> * <li>In case the Application Label is declared inside AndroidManifest.xml, the entered label is contained in it.</li> * </ul> * * @param data Data Structure of the Android Project. It serves for APKs and Android Projects. * @param deviceSpecs Device specifications for phones. * @param platformRules Rules and standards for the Android APi being used. * @param valManagerConfig App Validator Manager configuration. * @param results The results which will be returned from the validation performed in this method. * * @throws PreflightingCheckerException Exception thrown when there are unexpected problems validating * the Android Application. */ @Override public void execute(ApplicationData data, List<DeviceSpecification> deviceSpecs, ValidationManagerConfiguration valManagerConfig, ValidationResult results) throws PreflightingCheckerException { // get the label from AndroidManifest.xml XMLElement document = data.getManifestElement(); Document manifestDoc = document.getDocument(); Node labelNode = getLabelNode(manifestDoc); String androidLabelText = labelNode.getNodeValue(); // get entered parameter AndroidLabelChecker checker = (AndroidLabelChecker) getChecker(); parameterText = checker.getParameters().get(AndroidLabelChecker.PARAMETER_LABEL_TEXT).getValue(); if (parameterText == null) { PreflightingLogger .debug("Variable parameterText is null. Check if parameter \"labelText\" of checker androidLabel is being set."); } // handle case where the label is a resource identifier if (androidLabelText.startsWith(ANDROID_STRING_IDENTIFIER)) { analyzeLocalizedLabel(data, valManagerConfig, results, document, androidLabelText); } else { // the label is a hard coded text, check the string itself analyzeHardcodedLabel(valManagerConfig, results, document, labelNode, androidLabelText); } } /** * Verify if the label value contains the parameterText. */ private void analyzeHardcodedLabel(ValidationManagerConfiguration valManagerConfig, ValidationResult results, XMLElement document, Node labelNode, String androidLabelText) { if ((parameterText != null) && !androidLabelText.toLowerCase().contains(parameterText.toLowerCase())) { List<Integer> lineList = new ArrayList<Integer>(); int lineNumber = document.getNodeLineNumber(labelNode); if (lineNumber > 0) //Verify if line number is available. { lineList.add(lineNumber); } // create validation result - error structure ValidationResultData result = createValidationResult( AndroidLabelCheckerNLS.bind( AndroidLabelCheckerNLS.CorrectTextInLabelCondition_LabelNotContainedAndroidXML, parameterText), AndroidLabelCheckerNLS .bind(AndroidLabelCheckerNLS.CorrectTextInLabelCondition_AddTextInLabel, parameterText), valManagerConfig, document, parameterText, lineList); // add created result to the results list results.addValidationResult(result); } } /* * Verify if all string resources (from all locales) referred by the label resource identifier * contains the parameterText */ private void analyzeLocalizedLabel(ApplicationData data, ValidationManagerConfiguration valManagerConfig, ValidationResult results, XMLElement document, String androidLabelText) { // get resource identifier String resId = androidLabelText.replace(ANDROID_STRING_IDENTIFIER, ""); //$NON-NLS-1$ // get resource folder List<Element> folderResElements = ElementUtils.getElementByType(data.getRootElement(), Type.FOLDER_RES); ResourcesFolderElement resFolder = folderResElements.size() > 0 ? (ResourcesFolderElement) folderResElements.get(0) : null; // check default locale StringsElement defaultElements = resFolder.getDefaultValuesElement(); if (defaultElements != null) { Object value = defaultElements.getValue(resId); androidLabelText = (value != null) ? (String) value : ""; //$NON-NLS-1$ // execute the checker condition - the entered text must be within the label if ((parameterText != null) && !androidLabelText.toLowerCase().contains(parameterText.toLowerCase())) { // create validation result - error structure ValidationResultData result = createValidationResult( AndroidLabelCheckerNLS.bind( AndroidLabelCheckerNLS.CorrectTextInLabelCondition_LabelReferedAndroidXML, parameterText), AndroidLabelCheckerNLS .bind(AndroidLabelCheckerNLS.CorrectTextInLabelCondition_LabelReferedAndroidXMLDefaultLocale, parameterText), valManagerConfig, document, parameterText, new ArrayList<Integer>()); // add created result to the results list results.addValidationResult(result); } } // check non-default locales if ((resFolder != null) && (resFolder.getAvailableLocales() != null) && (resFolder.getAvailableLocales().size() > 0)) { for (Locale locale : resFolder.getAvailableLocales()) { String localeText = locale.getLanguage() + ((locale.getCountry() != null) && (locale.getCountry().length() > 0) ? "_" + locale.getCountry() : ""); //$NON-NLS-1$ //$NON-NLS-2$ // get the android label for each locale StringsElement stringsElement = resFolder.getValuesElement(locale); Object value = stringsElement.getValue(resId); androidLabelText = (value != null) ? (String) value : ""; //$NON-NLS-1$ // execute the checker condition - the entered text must be within the label if (!androidLabelText.toLowerCase().contains(parameterText.toLowerCase())) { // create validation result - error structure ValidationResultData result = createValidationResult( AndroidLabelCheckerNLS.bind( AndroidLabelCheckerNLS.CorrectTextInLabelCondition_LabelReferedAndroidXMLLocale, parameterText, localeText), AndroidLabelCheckerNLS .bind(AndroidLabelCheckerNLS.CorrectTextInLabelCondition_AddLabelAndroidXMLLocale, parameterText, localeText), valManagerConfig, document, parameterText, new ArrayList<Integer>()); // add created result to the results list results.addValidationResult(result); } } } } /** * In order to execute the checker, first several conditions must be verified. * <ul> * <li>There must be an AndroidManifest.xml file in the Android Project.</li> * <li>There must be a label in the AndroidManifest.xml file.</li> * <li>There must be a value for the parameter labelText.</li> * </ul> * * @param data Data structure holding all files, classes and resources of the * APK or Project. * @param deviceSpecs List of device specifications. * * @return Returns the {@link IStatus} which states whether the Checker * can be run. * * @throws PreflightingCheckerException Exception thrown in case there is any problem * verifying the conditions. */ @Override public CanExecuteConditionStatus canExecute(ApplicationData data, List<DeviceSpecification> deviceSpecs) throws PreflightingCheckerException { // first check the manifest file status CanExecuteConditionStatus status = CheckerUtils.isAndroidManifestFileExistent(data, getId()); // there must be parameters AndroidLabelChecker.PARAMETER_LABEL_TEXT set String labelParameterValue = getChecker().getParameters().get(AndroidLabelChecker.PARAMETER_LABEL_TEXT) .getValue(); if (labelParameterValue == null) { status = new CanExecuteConditionStatus(IStatus.INFO, AndroidLabelActivator.PLUGIN_ID, AndroidLabelCheckerNLS.CorrectTextInLabelCondition_NoEnteredParamWarn); } if (status.getSeverity() != IStatus.ERROR) { // there must be a parameter set AndroidLabelChecker checker = (AndroidLabelChecker) getChecker(); if ((checker.getParameters() != null) && checker.getParameters() .containsKey(AndroidLabelChecker.PARAMETER_LABEL_TEXT)) { XMLElement document = data.getManifestElement(); Document manifestDoc = document.getDocument(); // there must be a label in order to allow the checker execution Node labelNode = getLabelNode(manifestDoc); if ((labelNode != null) && (labelNode.getNodeValue() != null)) { status = new CanExecuteConditionStatus(IStatus.OK, getChecker().getId(), ""); //$NON-NLS-1$ } else { status = new CanExecuteConditionStatus( IStatus.ERROR, AndroidLabelActivator.PLUGIN_ID, AndroidLabelCheckerNLS.CorrectTextInLabelCondition_AndroidXMlMustHaveLabelRunChecker); } } else { status = new CanExecuteConditionStatus( IStatus.ERROR, AndroidLabelActivator.PLUGIN_ID, AndroidLabelCheckerNLS.CorrectTextInLabelCondition_ExecuteCheckerEnterLabelText); } } status.setConditionId(getId()); return status; } /** * Create the {@link ValidationResultData} which represents the error that * has occurred during the validation. It will be reported to the user. * * @param issueDescription The description of the error. * @param quickFixSuggestion The quick-fix-suggestion for the error. * @param valManagerConfig App Validator Manager configuration. * @param document AndroidManifest.xml in a {@link Document} object. * @param parameterText The text entered by the user as a parameter. * @param lineList The list of lines where the error has occurred. * * @return Returns the {@link ValidationResultData} which holds the error * to be displayed by the App Validator. */ private ValidationResultData createValidationResult(String issueDescription, String quickFixSuggestion, ValidationManagerConfiguration valManagerConfig, XMLElement document, String parameterText, List<Integer> lineList) { ValidationResultData result = new ValidationResultData(); result.setConditionID(getId()); result.addFileToIssueLines(document.getFile(), lineList); result.setIssueDescription(issueDescription); result.setQuickFixSuggestion(quickFixSuggestion); result.setInfoURL("http://developer.motorola.com/docstools/library/motodev-app-validator/#androidLabel-findTextInLabel"); //$NON-NLS-1$ result.setSeverity(getSeverityLevel()); return result; } /** * Get the label {@link Node} from the AndroidManifext.xml file * represented as a {@link Document}. * <br> * In case nothing is found, <code>null</code> is returned. * * @param manifestDoc AdnroidManifest.xml file as a {@link Document} where * the label will be sought. * * @return Returns the AndroidManifest.xml label {@link Node}. */ private Node getLabelNode(Document manifestDoc) { final int APPLICATION_NODE_INDEX = 0; Node labelNode = null; NodeList applicationNodes = manifestDoc.getElementsByTagName(MANIFEST_TAG_APPLICATION); // there must be one application note, get it Node applicationNode = applicationNodes.item(APPLICATION_NODE_INDEX); if (applicationNode != null) { // get label node labelNode = applicationNode.getAttributes().getNamedItem(MANIFEST_TAG_LABEL); } return labelNode; } }