• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.layoutopt.uix;
18 
19 import org.w3c.dom.Document;
20 import org.w3c.dom.Element;
21 import org.w3c.dom.Node;
22 import org.w3c.dom.NodeList;
23 import org.xml.sax.SAXException;
24 
25 import java.io.File;
26 import java.io.InputStream;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.IOException;
30 import java.net.URISyntaxException;
31 import java.net.URL;
32 import java.util.zip.ZipFile;
33 import java.util.zip.ZipEntry;
34 import java.util.Enumeration;
35 import java.util.List;
36 import java.util.ArrayList;
37 
38 import com.android.layoutopt.uix.xml.XmlDocumentBuilder;
39 import com.android.layoutopt.uix.rules.Rule;
40 import com.android.layoutopt.uix.rules.GroovyRule;
41 import com.android.layoutopt.uix.util.IOUtilities;
42 import groovy.lang.GroovyClassLoader;
43 import groovy.lang.GroovyShell;
44 import groovy.lang.Script;
45 
46 /**
47  * Analysis engine used to discover inefficiencies in Android XML
48  * layout documents.
49  *
50  * Anaylizing an Android XML layout produces a list of explicit messages
51  * as well as possible solutions.
52  */
53 public class LayoutAnalyzer {
54     private static final String RULES_PREFIX = "rules/";
55 
56     private final XmlDocumentBuilder mBuilder = new XmlDocumentBuilder();
57     private final List<Rule> mRules = new ArrayList<Rule>();
58 
59     /**
60      * Creates a new layout analyzer. This constructor takes no argument
61      * and will use the default options.
62      */
LayoutAnalyzer()63     public LayoutAnalyzer() {
64         loadRules();
65     }
66 
loadRules()67     private void loadRules() {
68         ClassLoader parent = getClass().getClassLoader();
69         GroovyClassLoader loader = new GroovyClassLoader(parent);
70         GroovyShell shell = new GroovyShell(loader);
71 
72         URL jar = getClass().getProtectionDomain().getCodeSource().getLocation();
73         ZipFile zip = null;
74         try {
75             zip = new ZipFile(new File(jar.toURI()));
76             Enumeration<? extends ZipEntry> entries = zip.entries();
77             while (entries.hasMoreElements()) {
78                 ZipEntry entry = entries.nextElement();
79                 if (!entry.isDirectory() && entry.getName().startsWith(RULES_PREFIX)) {
80                     loadRule(shell, entry.getName(), zip.getInputStream(entry));
81                 }
82             }
83         } catch (IOException e) {
84             e.printStackTrace();
85         } catch (URISyntaxException e) {
86             e.printStackTrace();
87         } finally {
88             try {
89                 if (zip != null) zip.close();
90             } catch (IOException e) {
91                 // Ignore
92             }
93         }
94     }
95 
loadRule(GroovyShell shell, String name, InputStream stream)96     private void loadRule(GroovyShell shell, String name, InputStream stream) {
97         try {
98             Script script = shell.parse(stream);
99             mRules.add(new GroovyRule(name, script));
100         } catch (Exception e) {
101             System.err.println("Could not load rule " + name + ":");
102             e.printStackTrace();
103         } finally {
104             IOUtilities.close(stream);
105         }
106     }
107 
addRule(Rule rule)108     public void addRule(Rule rule) {
109         if (rule == null) {
110             throw new IllegalArgumentException("A rule must be non-null");
111         }
112         mRules.add(rule);
113     }
114 
115     /**
116      * Analyzes the specified file.
117      *
118      * @param file The file to analyze.
119      *
120      * @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
121      *         cannot be null.
122      */
analyze(File file)123     public LayoutAnalysis analyze(File file) {
124         if (file != null && file.exists()) {
125             InputStream in = null;
126             try {
127                 in = new FileInputStream(file);
128                 return analyze(file.getPath(), in);
129             } catch (FileNotFoundException e) {
130                 // Ignore, cannot happen
131             } finally {
132                 IOUtilities.close(in);
133             }
134         }
135 
136         return LayoutAnalysis.ERROR;
137     }
138 
139     /**
140      * Analyzes the specified XML stream.
141      *
142      * @param stream The stream to analyze.
143      * @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
144      *         cannot be null.
145      */
analyze(InputStream stream)146     public LayoutAnalysis analyze(InputStream stream) {
147         return analyze("<unknown>", stream);
148     }
149 
analyze(String name, InputStream stream)150     private LayoutAnalysis analyze(String name, InputStream stream) {
151          try {
152              Document document = mBuilder.parse(stream);
153              return analyze(name, document);
154          } catch (SAXException e) {
155              // Ignore
156          } catch (IOException e) {
157              // Ignore
158          }
159          return LayoutAnalysis.ERROR;
160     }
161 
162     /**
163      * Analyzes the specified XML document.
164      *
165      * @param content The XML document to analyze.
166      *
167      * @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
168      *         cannot be null.
169      */
analyze(String content)170     public LayoutAnalysis analyze(String content) {
171          return analyze("<unknown>", content);
172     }
173 
174     /**
175      * Analyzes the specified XML document.
176      *
177      * @param name The name of the document.
178      * @param content The XML document to analyze.
179      *
180      * @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
181      *         cannot be null.
182      */
analyze(String name, String content)183     public LayoutAnalysis analyze(String name, String content) {
184          try {
185              Document document = mBuilder.parse(content);
186              return analyze(name, document);
187          } catch (SAXException e) {
188              // Ignore
189          } catch (IOException e) {
190              // Ignore
191          }
192          return LayoutAnalysis.ERROR;
193     }
194 
195     /**
196      * Analyzes the specified XML document.
197      *
198      * @param document The XML document to analyze.
199      *
200      * @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
201      *         cannot be null.
202      */
analyze(Document document)203     public LayoutAnalysis analyze(Document document) {
204         return analyze("<unknown>", document);
205     }
206 
207     /**
208      * Analyzes the specified XML document.
209      *
210      * @param name The name of the document.
211      * @param document The XML document to analyze.
212      *
213      * @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
214      *         cannot be null.
215      */
analyze(String name, Document document)216     public LayoutAnalysis analyze(String name, Document document) {
217         LayoutAnalysis analysis = new LayoutAnalysis(name);
218 
219         try {
220             Element root = document.getDocumentElement();
221             analyze(analysis, root);
222         } finally {
223             analysis.validate();
224         }
225 
226         return analysis;
227     }
228 
analyze(LayoutAnalysis analysis, Node node)229     private void analyze(LayoutAnalysis analysis, Node node) {
230         NodeList list = node.getChildNodes();
231         int count = list.getLength();
232 
233         applyRules(analysis, node);
234 
235         for (int i = 0; i < count; i++) {
236             Node child = list.item(i);
237             if (child.getNodeType() == Node.ELEMENT_NODE) {
238                 analyze(analysis, child);
239             }
240         }
241     }
242 
applyRules(LayoutAnalysis analysis, Node node)243     private void applyRules(LayoutAnalysis analysis, Node node) {
244         analysis.setCurrentNode(node);
245         for (Rule rule : mRules) {
246             rule.run(analysis, node);
247         }
248     }
249 }
250