/AccessCheck/src/com/android/accessibility/AccessibilityValidationContentHandler.java
http://eyes-free.googlecode.com/ · Java · 214 lines · 101 code · 21 blank · 92 comment · 16 complexity · e319509ca5e71ffe61360f684af2abf8 MD5 · raw file
- /*
- * Copyright 2010 Google Inc.
- *
- * 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.android.accessibility;
- import org.xml.sax.Attributes;
- import org.xml.sax.Locator;
- import org.xml.sax.helpers.DefaultHandler;
- import java.io.File;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.net.URLClassLoader;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- import java.util.logging.Logger;
- /**
- * An object that handles Android xml layout files in conjunction with an
- * XMLParser for the purpose of testing for accessibility based on the following
- * rule:
- * <p>
- * If the Element tag is ImageView (or a subclass of ImageView), then the tag
- * must contain a contentDescription attribute.
- * <p>
- * This class also has logic to ascertain the subclasses of ImageView and thus
- * requires the path to an Android sdk jar. The subclasses are saved for
- * application of the above rule when a new XML document tag needs processing.
- *
- * @author dtseng@google.com (David Tseng)
- */
- public class AccessibilityValidationContentHandler extends DefaultHandler {
- /** Used to obtain line information within the XML file. */
- private Locator mLocator;
- /** The location of the file we are handling. */
- private final String mPath;
- /** The total number of errors within the current file. */
- private int mValidationErrors = 0;
- /**
- * Element tags we have seen before and determined not to be
- * subclasses of ImageView.
- */
- private final Set<String> mExclusionList = new HashSet<String>();
- /** The path to the Android sdk jar file. */
- private final File mAndroidSdkPath;
- /**
- * The ImageView class stored for easy comparison while handling content. It
- * gets initialized in the {@link AccessibilityValidationHandler}
- * constructor if not already done so.
- */
- private static Class<?> sImageViewElement;
- /**
- * A class loader properly initialized and reusable across files. It gets
- * initialized in the {@link AccessibilityValidationHandler} constructor if
- * not already done so.
- */
- private static ClassLoader sValidationClassLoader;
- /** Attributes we test existence for (for example, contentDescription). */
- private static final HashSet<String> sExpectedAttributes =
- new HashSet<String>();
- /** The object that handles our logging. */
- private static final Logger sLogger = Logger.getLogger("android.accessibility");
- /**
- * Construct an AccessibilityValidationContentHandler object with the file
- * on which validation occurs and a path to the Android sdk jar. Then,
- * initialize the class members if not previously done so.
- *
- * @throws IllegalArgumentException
- * when given an invalid Android sdk path or when unable to
- * locate {@link ImageView} class.
- */
- public AccessibilityValidationContentHandler(String fullyQualifiedPath,
- File androidSdkPath) throws IllegalArgumentException {
- mPath = fullyQualifiedPath;
- mAndroidSdkPath = androidSdkPath;
- initializeAccessibilityValidationContentHandler();
- }
- /**
- * Used to log line numbers of errors in {@link #startElement}.
- */
- @Override
- public void setDocumentLocator(Locator locator) {
- mLocator = locator;
- }
- /**
- * For each subclass of ImageView, test for existence of the specified
- * attributes.
- */
- @Override
- public void startElement(String uri, String localName, String qName,
- Attributes atts) {
- Class<?> potentialClass;
- String classPath = "android.widget." + localName;
- try {
- potentialClass = sValidationClassLoader.loadClass(classPath);
- } catch (ClassNotFoundException cnfException) {
- return; // do nothing as the class doesn't exist.
- }
- // if we already determined this class path isn't a subclass of
- // ImageView, skip it.
- // Otherwise, check to see if it is a subclass.
- if (mExclusionList.contains(classPath)) {
- return;
- } else if (!sImageViewElement.isAssignableFrom(potentialClass)) {
- mExclusionList.add(classPath);
- return;
- }
- boolean hasAttribute = false;
- StringBuilder extendedOutput = new StringBuilder();
- for (int i = 0; i < atts.getLength(); i++) {
- String currentAttribute = atts.getLocalName(i).toLowerCase();
- if (sExpectedAttributes.contains(currentAttribute)) {
- hasAttribute = true;
- break;
- } else if (currentAttribute.equals("id")) {
- extendedOutput.append("|id=" + currentAttribute);
- } else if (currentAttribute.equals("src")) {
- extendedOutput.append("|src=" + atts.getValue(i));
- }
- }
- if (!hasAttribute) {
- if (getValidationErrors() == 0) {
- sLogger.info(mPath);
- }
- sLogger.info(String.format("ln: %s. Error in %s%s tag.",
- mLocator.getLineNumber(), localName, extendedOutput));
- mValidationErrors++;
- }
- }
- /**
- * Returns the total number of errors encountered in this file.
- */
- public int getValidationErrors() {
- return mValidationErrors;
- }
- /**
- * Set the class loader and ImageView class objects that will be used during
- * the startElement validation logic. The class loader encompasses the class
- * paths provided.
- *
- * @throws ClassNotFoundException
- * when the ImageView Class object could not be found within the
- * provided class loader.
- */
- public static void setClassLoaderAndBaseClass(URL[] urlSearchPaths)
- throws ClassNotFoundException {
- sValidationClassLoader = new URLClassLoader(urlSearchPaths);
- sImageViewElement =
- sValidationClassLoader.loadClass("android.widget.ImageView");
- }
- /**
- * Adds an attribute that will be tested for existence in
- * {@link #startElement}. The search will always be case-insensitive.
- */
- private static void addExpectedAttribute(String attribute) {
- sExpectedAttributes.add(attribute.toLowerCase());
- }
- /**
- * Initializes the class loader and {@link ImageView} Class objects.
- *
- * @throws IllegalArgumentException
- * when either an invalid path is provided or ImageView cannot
- * be found in the classpaths.
- */
- private void initializeAccessibilityValidationContentHandler()
- throws IllegalArgumentException {
- if (sValidationClassLoader != null && sImageViewElement != null) {
- return; // These objects are already initialized.
- }
- try {
- setClassLoaderAndBaseClass(new URL[] { mAndroidSdkPath.toURL() });
- } catch (MalformedURLException mUException) {
- throw new IllegalArgumentException("invalid android sdk path",
- mUException);
- } catch (ClassNotFoundException cnfException) {
- throw new IllegalArgumentException(
- "Unable to find ImageView class.", cnfException);
- }
- // Add all of the expected attributes.
- addExpectedAttribute("contentDescription");
- }
- }