PageRenderTime 10ms CodeModel.GetById 1ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 1ms

/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
  1/*
  2 * Copyright 2010 Google Inc.
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5 * use this file except in compliance with the License. You may obtain a copy of
  6 * the License at
  7 *
  8 * http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations under
 14 * the License.
 15 */
 16package com.android.accessibility;
 17
 18import org.xml.sax.Attributes;
 19import org.xml.sax.Locator;
 20import org.xml.sax.helpers.DefaultHandler;
 21
 22import java.io.File;
 23import java.net.MalformedURLException;
 24import java.net.URL;
 25import java.net.URLClassLoader;
 26import java.util.ArrayList;
 27import java.util.HashSet;
 28import java.util.List;
 29import java.util.Set;
 30import java.util.logging.Logger;
 31
 32/**
 33 * An object that handles Android xml layout files in conjunction with an
 34 * XMLParser for the purpose of testing for accessibility based on the following
 35 * rule:
 36 * <p>
 37 * If the Element tag is ImageView (or a subclass of ImageView), then the tag
 38 * must contain a contentDescription attribute.
 39 * <p>
 40 * This class also has logic to ascertain the subclasses of ImageView and thus
 41 * requires the path to an Android sdk jar. The subclasses are saved for
 42 * application of the above rule when a new XML document tag needs processing.
 43 *
 44 * @author dtseng@google.com (David Tseng)
 45 */
 46public class AccessibilityValidationContentHandler extends DefaultHandler {
 47    /** Used to obtain line information within the XML file. */
 48    private Locator mLocator;
 49    /** The location of the file we are handling. */
 50    private final String mPath;
 51    /** The total number of errors within the current file. */
 52    private int mValidationErrors = 0;
 53
 54    /**
 55     * Element tags we have seen before and determined not to be
 56     * subclasses of ImageView.
 57     */
 58    private final Set<String> mExclusionList = new HashSet<String>();
 59
 60    /** The path to the Android sdk jar file. */
 61    private final File mAndroidSdkPath;
 62
 63    /**
 64     * The ImageView class stored for easy comparison while handling content. It
 65     * gets initialized in the {@link AccessibilityValidationHandler}
 66     * constructor if not already done so.
 67     */
 68    private static Class<?> sImageViewElement;
 69
 70    /**
 71     * A class loader properly initialized and reusable across files. It gets
 72     * initialized in the {@link AccessibilityValidationHandler} constructor if
 73     * not already done so.
 74     */
 75    private static ClassLoader sValidationClassLoader;
 76
 77    /** Attributes we test existence for (for example, contentDescription). */
 78    private static final HashSet<String> sExpectedAttributes =
 79            new HashSet<String>();
 80
 81    /** The object that handles our logging. */
 82    private static final Logger sLogger = Logger.getLogger("android.accessibility");
 83
 84    /**
 85     * Construct an AccessibilityValidationContentHandler object with the file
 86     * on which validation occurs and a path to the Android sdk jar. Then,
 87     * initialize the class members if not previously done so.
 88     * 
 89     * @throws IllegalArgumentException
 90     *             when given an invalid Android sdk path or when unable to
 91     *             locate {@link ImageView} class.
 92     */
 93    public AccessibilityValidationContentHandler(String fullyQualifiedPath,
 94            File androidSdkPath) throws IllegalArgumentException {
 95        mPath = fullyQualifiedPath;
 96        mAndroidSdkPath = androidSdkPath;
 97
 98        initializeAccessibilityValidationContentHandler();
 99    }
100
101    /**
102     * Used to log line numbers of errors in {@link #startElement}.
103     */
104    @Override
105    public void setDocumentLocator(Locator locator) {
106        mLocator = locator;
107    }
108
109    /**
110     * For each subclass of ImageView, test for existence of the specified
111     * attributes.
112     */
113    @Override
114    public void startElement(String uri, String localName, String qName,
115            Attributes atts) {
116        Class<?> potentialClass;
117        String classPath = "android.widget." + localName;
118        try {
119            potentialClass = sValidationClassLoader.loadClass(classPath);
120        } catch (ClassNotFoundException cnfException) {
121            return; // do nothing as the class doesn't exist.
122        }
123
124        // if we already determined this class path isn't a subclass of
125        // ImageView, skip it.
126        // Otherwise, check to see if it is a subclass.
127        if (mExclusionList.contains(classPath)) {
128            return;
129        } else if (!sImageViewElement.isAssignableFrom(potentialClass)) {
130            mExclusionList.add(classPath);
131            return;
132        }
133
134        boolean hasAttribute = false;
135        StringBuilder extendedOutput = new StringBuilder();
136        for (int i = 0; i < atts.getLength(); i++) {
137            String currentAttribute = atts.getLocalName(i).toLowerCase();
138            if (sExpectedAttributes.contains(currentAttribute)) {
139                hasAttribute = true;
140                break;
141            } else if (currentAttribute.equals("id")) {
142                extendedOutput.append("|id=" + currentAttribute);
143            } else if (currentAttribute.equals("src")) {
144                extendedOutput.append("|src=" + atts.getValue(i));
145            }
146        }
147
148        if (!hasAttribute) {
149            if (getValidationErrors() == 0) {
150                sLogger.info(mPath);
151            }
152            sLogger.info(String.format("ln: %s.  Error in %s%s tag.", 
153                    mLocator.getLineNumber(), localName, extendedOutput));
154            mValidationErrors++;
155        }
156    }
157
158    /**
159     * Returns the total number of errors encountered in this file.
160     */
161    public int getValidationErrors() {
162        return mValidationErrors;
163    }
164
165    /**
166     * Set the class loader and ImageView class objects that will be used during
167     * the startElement validation logic. The class loader encompasses the class
168     * paths provided.
169     * 
170     * @throws ClassNotFoundException
171     *             when the ImageView Class object could not be found within the
172     *             provided class loader.
173     */
174    public static void setClassLoaderAndBaseClass(URL[] urlSearchPaths)
175            throws ClassNotFoundException {
176        sValidationClassLoader = new URLClassLoader(urlSearchPaths);
177        sImageViewElement =
178            sValidationClassLoader.loadClass("android.widget.ImageView");
179    }
180
181    /**
182     * Adds an attribute that will be tested for existence in
183     * {@link #startElement}. The search will always be case-insensitive.
184     */
185    private static void addExpectedAttribute(String attribute) {
186        sExpectedAttributes.add(attribute.toLowerCase());
187    }
188
189    /**
190     * Initializes the class loader and {@link ImageView} Class objects.
191     * 
192     * @throws IllegalArgumentException
193     *             when either an invalid path is provided or ImageView cannot
194     *             be found in the classpaths.
195     */
196    private void initializeAccessibilityValidationContentHandler()
197            throws IllegalArgumentException {
198        if (sValidationClassLoader != null && sImageViewElement != null) {
199            return; // These objects are already initialized.
200        }
201        try {
202            setClassLoaderAndBaseClass(new URL[] { mAndroidSdkPath.toURL() });
203        } catch (MalformedURLException mUException) {
204            throw new IllegalArgumentException("invalid android sdk path",
205                    mUException);
206        } catch (ClassNotFoundException cnfException) {
207            throw new IllegalArgumentException(
208                    "Unable to find ImageView class.", cnfException);
209        }
210
211        // Add all of the expected attributes.
212        addExpectedAttribute("contentDescription");
213    }
214}