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  */
16 
17 package com.android.accessibility;
18 
19 import org.xml.sax.InputSource;
20 import org.xml.sax.SAXException;
21 import org.xml.sax.XMLReader;
22 import org.xml.sax.helpers.XMLReaderFactory;
23 
24 import java.io.*;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.logging.Logger;
28 
29 /**
30  * An object that fetches all Android layout files and manages the testing of
31  * the files with the use of the AccessibilityValidationContentHandler. This
32  * object also reports on any errors encountered during the testing.
33  *
34  * @author dtseng@google.com (David Tseng)
35  */
36 public class AccessibilityValidator {
37     /** The root path to scan for Android layout files. */
38     private final File mRootFilePath;
39     /** Errors generated by thrown exceptions (and not by validation errors). */
40     private final List<String> mGeneralErrors = new ArrayList<String>();
41     /** A list of files we wish to have tested. */
42     private List<InputSource> mLayoutFiles;
43     /** The total number of validation test errors across all files. */
44     private int mTotalValidationErrors = 0;
45     /** The path to the Android sdk jar. */
46     private final File mAndroidSdkPath;
47 
48     /** The object that handles our logging. */
49     private static final Logger sLogger = Logger.getLogger("android.accessibility");
50 
51     /**
52      * The entry point to this tool.
53      *
54      * @param <args>
55      *            path on which to search for layout xml files that need
56      *            validation
57      */
main(String[] args)58     public static void main(String[] args) {
59         sLogger.info("AccessibilityValidator");
60         if (args.length == 2) {
61             sLogger.info("Validating classes using android jar for subclasses of ImageView");
62             new AccessibilityValidator(args[0], args[1]).run();
63         } else {
64             sLogger.info("Usage: java AccessibilityValidator <path> <Android jar path>");
65             return;
66         }
67     }
68 
69     /**
70      * Constructs an AccessibilityValidator object using the root path and the
71      * android jar path.
72      */
AccessibilityValidator(String rootPath, String androidSdkPath)73     public AccessibilityValidator(String rootPath, String androidSdkPath) {
74         mRootFilePath = new File(rootPath);
75         mAndroidSdkPath = new File(androidSdkPath);
76 
77         if (!mRootFilePath.exists()) {
78             throw new IllegalArgumentException("Invalid root path specified "
79                     + rootPath);
80         } else if (!mAndroidSdkPath.exists()) {
81             throw new IllegalArgumentException(
82                     "Invalid android sdk path specified " + androidSdkPath);
83         }
84     }
85 
86     /**
87      * Performs validation of Android layout files and logs errors that have
88      * been encountered during the validation. Returns true if the validation
89      * passes.
90      */
run()91     private boolean run() {
92         sLogger.info("Validating files under " + mRootFilePath);
93         mLayoutFiles = findLayoutFiles(mRootFilePath);
94         validateFiles();
95         for (String error : mGeneralErrors) {
96             sLogger.info(error);
97         }
98         sLogger.info("done with validation");
99         return mGeneralErrors.size() == 0;
100     }
101 
102     /**
103      * Accumulates a list of files under the path that meet two constraints.
104      * Firstly, it has a containing (parent) directory of "layout". Secondly, it
105      * has an xml extension
106      */
findLayoutFiles(File directory)107     private List<InputSource> findLayoutFiles(File directory) {
108         List<InputSource> layoutFiles = new ArrayList<InputSource>();
109 
110         for (File file : directory.listFiles()) {
111             // The file is a directory; recurse on the file.
112             if (file.isDirectory()) {
113                 List<InputSource> directoryFiles = findLayoutFiles(file);
114                 layoutFiles.addAll(directoryFiles);
115                 // Does the containing directory and filename meet our
116                 // constraints?
117             } else if (directory.getName().toLowerCase().contains("layout")
118                     && file.getName().toLowerCase().endsWith(".xml")) {
119                 InputSource addition;
120                 try {
121                     addition = new InputSource(new FileReader(file));
122                     // Store this explicitly for logging.
123                     addition.setPublicId(file.toString());
124                     layoutFiles.add(addition);
125                 } catch (FileNotFoundException fileNotFoundException) {
126                     mGeneralErrors.add("File not found "
127                             + fileNotFoundException);
128                 }
129             }
130         }
131 
132         return layoutFiles;
133     }
134 
135     /*
136      * Processes a list of files via an AccessibilityValidationContentHandler.
137      * The caller will only be notified of errors via logging.
138      */
validateFiles()139     public void validateFiles() {
140         sLogger.info("Validating " + getLayoutFiles().size());
141         XMLReader reader;
142         try {
143             reader = XMLReaderFactory.createXMLReader();
144         } catch (SAXException saxExp) {
145             mGeneralErrors.add("Error " + saxExp);
146             return;
147         }
148         for (InputSource file : getLayoutFiles()) {
149             try {
150                 AccessibilityValidationContentHandler contentHandler
151                 = new AccessibilityValidationContentHandler(
152                         file.getPublicId(), mAndroidSdkPath);
153                 reader.setContentHandler(contentHandler);
154                 reader.parse(file);
155                 mTotalValidationErrors += contentHandler.getValidationErrors();
156             } catch (IOException ioExp) {
157                 mGeneralErrors.add("Error reading file " + ioExp);
158             } catch (SAXException saxExp) {
159                 mGeneralErrors.add("Error " + saxExp);
160             }
161         }
162     }
163 
164     /**
165      * Returns the number of general errors (considered caught exceptions).
166      */
getGeneralErrors()167     public List<String> getGeneralErrors() {
168         return mGeneralErrors;
169     }
170 
171     /**
172      * Sets the files to be tested.
173      */
setLayoutFiles(List<InputSource> layoutFiles)174     public void setLayoutFiles(List<InputSource> layoutFiles) {
175         this.mLayoutFiles = layoutFiles;
176     }
177 
178     /**
179      * Gets the files to be tested.
180      */
getLayoutFiles()181     public List<InputSource> getLayoutFiles() {
182         return mLayoutFiles;
183     }
184 
185     /**
186      * Gets the total number of test validation errors across all files.
187      */
getTotalValidationErrors()188     public int getTotalValidationErrors() {
189         return mTotalValidationErrors;
190     }
191 }
192