diff options
Diffstat (limited to 'attribute_stats/src/Analyzer.java')
-rw-r--r-- | attribute_stats/src/Analyzer.java | 663 |
1 files changed, 0 insertions, 663 deletions
diff --git a/attribute_stats/src/Analyzer.java b/attribute_stats/src/Analyzer.java deleted file mode 100644 index 8da53ea38..000000000 --- a/attribute_stats/src/Analyzer.java +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php - * - * 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. - */ - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -/** - * Gathers statistics about attribute usage in layout files. This is how the "topAttrs" - * attributes listed in ADT's extra-view-metadata.xml (which drives the common attributes - * listed in the top of the context menu) is determined by running this script on a body - * of sample layout code. - * <p> - * This program takes one or more directory paths, and then it searches all of them recursively - * for layout files that are not in folders containing the string "test", and computes and - * prints frequency statistics. - */ -public class Analyzer { - /** Number of attributes to print for each view */ - public static final int ATTRIBUTE_COUNT = 6; - /** Separate out any attributes that constitute less than N percent of the total */ - public static final int THRESHOLD = 10; // percent - - private List<File> mDirectories; - private File mCurrentFile; - private boolean mListAdvanced; - - /** Map from view id to map from attribute to frequency count */ - private Map<String, Map<String, Usage>> mFrequencies = - new HashMap<String, Map<String, Usage>>(100); - - private Map<String, Map<String, Usage>> mLayoutAttributeFrequencies = - new HashMap<String, Map<String, Usage>>(100); - - private Map<String, String> mTopAttributes = new HashMap<String, String>(100); - private Map<String, String> mTopLayoutAttributes = new HashMap<String, String>(100); - - private int mFileVisitCount; - private int mLayoutFileCount; - private File mXmlMetadataFile; - - private Analyzer(List<File> directories, File xmlMetadataFile, boolean listAdvanced) { - mDirectories = directories; - mXmlMetadataFile = xmlMetadataFile; - mListAdvanced = listAdvanced; - } - - public static void main(String[] args) { - if (args.length < 1) { - System.err.println("Usage: " + Analyzer.class.getSimpleName() - + " <directory1> [directory2 [directory3 ...]]\n"); - System.err.println("Recursively scans for layouts in the given directory and"); - System.err.println("computes statistics about attribute frequencies."); - System.exit(-1); - } - - File metadataFile = null; - List<File> directories = new ArrayList<File>(); - boolean listAdvanced = false; - for (int i = 0, n = args.length; i < n; i++) { - String arg = args[i]; - - if (arg.equals("--list")) { - // List ALL encountered attributes - listAdvanced = true; - continue; - } - - // The -metadata flag takes a pointer to an ADT extra-view-metadata.xml file - // and attempts to insert topAttrs attributes into it (and saves it as same - // file +.mod as an extension). This isn't listed on the usage flag because - // it's pretty brittle and requires some manual fixups to the file afterwards. - if (arg.equals("--metadata")) { - i++; - File file = new File(args[i]); - if (!file.exists()) { - System.err.println(file.getName() + " does not exist"); - System.exit(-5); - } - if (!file.isFile() || !file.getName().endsWith(".xml")) { - System.err.println(file.getName() + " must be an XML file"); - System.exit(-4); - } - metadataFile = file; - continue; - } - File directory = new File(arg); - if (!directory.exists()) { - System.err.println(directory.getName() + " does not exist"); - System.exit(-2); - } - - if (!directory.isDirectory()) { - System.err.println(directory.getName() + " is not a directory"); - System.exit(-3); - } - - directories.add(directory); - } - - new Analyzer(directories, metadataFile, listAdvanced).analyze(); - } - - private void analyze() { - for (File directory : mDirectories) { - scanDirectory(directory); - } - - if (mListAdvanced) { - listAdvanced(); - } - - printStatistics(); - - if (mXmlMetadataFile != null) { - printMergedMetadata(); - } - } - - private void scanDirectory(File directory) { - File[] files = directory.listFiles(); - if (files == null) { - return; - } - - for (File file : files) { - mFileVisitCount++; - if (mFileVisitCount % 50000 == 0) { - System.out.println("Analyzed " + mFileVisitCount + " files..."); - } - - if (file.isFile()) { - scanFile(file); - } else if (file.isDirectory()) { - // Skip stuff related to tests - if (file.getName().contains("test")) { - continue; - } - - // Recurse over subdirectories - scanDirectory(file); - } - } - } - - private void scanFile(File file) { - if (file.getName().endsWith(".xml")) { - File parent = file.getParentFile(); - if (parent.getName().startsWith("layout")) { - analyzeLayout(file); - } - } - - } - - private void analyzeLayout(File file) { - mCurrentFile = file; - mLayoutFileCount++; - Document document = null; - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - InputSource is = new InputSource(new StringReader(readFile(file))); - try { - factory.setNamespaceAware(true); - factory.setValidating(false); - DocumentBuilder builder = factory.newDocumentBuilder(); - document = builder.parse(is); - - analyzeDocument(document); - - } catch (ParserConfigurationException e) { - // pass -- ignore files we can't parse - } catch (SAXException e) { - // pass -- ignore files we can't parse - } catch (IOException e) { - // pass -- ignore files we can't parse - } - } - - - private void analyzeDocument(Document document) { - analyzeElement(document.getDocumentElement()); - } - - private void analyzeElement(Element element) { - if (element.getTagName().equals("item")) { - // Resource files shouldn't be in the layout/ folder but I came across - // some cases - System.out.println("Warning: found <item> tag in a layout file in " - + mCurrentFile.getPath()); - return; - } - - countAttributes(element); - countLayoutAttributes(element); - - // Recurse over children - NodeList childNodes = element.getChildNodes(); - for (int i = 0, n = childNodes.getLength(); i < n; i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - analyzeElement((Element) child); - } - } - } - - private void countAttributes(Element element) { - String tag = element.getTagName(); - Map<String, Usage> attributeMap = mFrequencies.get(tag); - if (attributeMap == null) { - attributeMap = new HashMap<String, Usage>(70); - mFrequencies.put(tag, attributeMap); - } - - NamedNodeMap attributes = element.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attribute = attributes.item(i); - String name = attribute.getNodeName(); - - if (name.startsWith("android:layout_")) { - // Skip layout attributes; they are a function of the parent layout that this - // view is embedded within, not the view itself. - // TODO: Consider whether we should incorporate this info or make statistics - // about that as well? - continue; - } - - if (name.equals("android:id")) { - // Skip ids: they are (mostly) unrelated to the view type and the tool - // already offers id editing prominently - continue; - } - - if (name.startsWith("xmlns:")) { - // Unrelated to frequency counts - continue; - } - - Usage usage = attributeMap.get(name); - if (usage == null) { - usage = new Usage(name); - } else { - usage.incrementCount(); - } - attributeMap.put(name, usage); - } - } - - private void countLayoutAttributes(Element element) { - String parentTag = element.getParentNode().getNodeName(); - Map<String, Usage> attributeMap = mLayoutAttributeFrequencies.get(parentTag); - if (attributeMap == null) { - attributeMap = new HashMap<String, Usage>(70); - mLayoutAttributeFrequencies.put(parentTag, attributeMap); - } - - NamedNodeMap attributes = element.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attribute = attributes.item(i); - String name = attribute.getNodeName(); - - if (!name.startsWith("android:layout_")) { - continue; - } - - // Skip layout_width and layout_height; they are mandatory in all but GridLayout so not - // very interesting - if (name.equals("android:layout_width") || name.equals("android:layout_height")) { - continue; - } - - Usage usage = attributeMap.get(name); - if (usage == null) { - usage = new Usage(name); - } else { - usage.incrementCount(); - } - attributeMap.put(name, usage); - } - } - - // Copied from AdtUtils - private static String readFile(File file) { - try { - return readFile(new FileReader(file)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - - return null; - } - - private static String readFile(Reader inputStream) { - BufferedReader reader = null; - try { - reader = new BufferedReader(inputStream); - StringBuilder sb = new StringBuilder(2000); - while (true) { - int c = reader.read(); - if (c == -1) { - return sb.toString(); - } else { - sb.append((char)c); - } - } - } catch (IOException e) { - // pass -- ignore files we can't read - } finally { - try { - if (reader != null) { - reader.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - return null; - } - - private void printStatistics() { - System.out.println("Analyzed " + mLayoutFileCount - + " layouts (in a directory trees containing " + mFileVisitCount + " files)"); - System.out.println("Top " + ATTRIBUTE_COUNT - + " for each view (excluding layout_ attributes) :"); - System.out.println("\n"); - System.out.println(" Rank Count Share Attribute"); - System.out.println("========================================================="); - List<String> views = new ArrayList<String>(mFrequencies.keySet()); - Collections.sort(views); - for (String view : views) { - String top = processUageMap(view, mFrequencies.get(view)); - if (top != null) { - mTopAttributes.put(view, top); - } - } - - System.out.println("\n\n\nTop " + ATTRIBUTE_COUNT + " layout attributes (excluding " - + "mandatory layout_width and layout_height):"); - System.out.println("\n"); - System.out.println(" Rank Count Share Attribute"); - System.out.println("========================================================="); - views = new ArrayList<String>(mLayoutAttributeFrequencies.keySet()); - Collections.sort(views); - for (String view : views) { - String top = processUageMap(view, mLayoutAttributeFrequencies.get(view)); - if (top != null) { - mTopLayoutAttributes.put(view, top); - } - } - } - - private static String processUageMap(String view, Map<String, Usage> map) { - if (map == null) { - return null; - } - - if (view.indexOf('.') != -1 && !view.startsWith("android.")) { - // Skip custom views - return null; - } - - List<Usage> values = new ArrayList<Usage>(map.values()); - if (values.size() == 0) { - return null; - } - - Collections.sort(values); - int totalCount = 0; - for (Usage usage : values) { - totalCount += usage.count; - } - - System.out.println("\n<" + view + ">:"); - if (view.equals("#document")) { - System.out.println("(Set on root tag, probably intended for included context)"); - } - - int place = 1; - int count = 0; - int prevCount = -1; - float prevPercentage = 0f; - StringBuilder sb = new StringBuilder(); - for (Usage usage : values) { - if (count++ >= ATTRIBUTE_COUNT && usage.count < prevCount) { - break; - } - - float percentage = 100 * usage.count/(float)totalCount; - if (percentage < THRESHOLD && prevPercentage >= THRESHOLD) { - System.out.println(" -----Less than 10%-------------------------------------"); - } - System.out.printf(" %1d. %5d %5.1f%% %s\n", place, usage.count, - percentage, usage.attribute); - - prevPercentage = percentage; - if (prevCount != usage.count) { - prevCount = usage.count; - place++; - } - - if (percentage >= THRESHOLD /*&& usage.count > 1*/) { // 1:Ignore when not enough data? - if (sb.length() > 0) { - sb.append(','); - } - String name = usage.attribute; - if (name.startsWith("android:")) { - name = name.substring("android:".length()); - } - sb.append(name); - } - } - - return sb.length() > 0 ? sb.toString() : null; - } - - private void printMergedMetadata() { - assert mXmlMetadataFile != null; - String metadata = readFile(mXmlMetadataFile); - if (metadata == null || metadata.length() == 0) { - System.err.println("Invalid metadata file"); - System.exit(-6); - } - - System.err.flush(); - System.out.println("\n\nUpdating layout metadata file..."); - System.out.flush(); - - StringBuilder sb = new StringBuilder((int) (2 * mXmlMetadataFile.length())); - String[] lines = metadata.split("\n"); - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; - sb.append(line).append('\n'); - int classIndex = line.indexOf("class=\""); - if (classIndex != -1) { - int start = classIndex + "class=\"".length(); - int end = line.indexOf('"', start + 1); - if (end != -1) { - String view = line.substring(start, end); - if (view.startsWith("android.widget.")) { - view = view.substring("android.widget.".length()); - } else if (view.startsWith("android.view.")) { - view = view.substring("android.view.".length()); - } else if (view.startsWith("android.webkit.")) { - view = view.substring("android.webkit.".length()); - } - String top = mTopAttributes.get(view); - if (top == null) { - System.err.println("Warning: No frequency data for view " + view); - } else { - sb.append(line.substring(0, classIndex)); // Indentation - - sb.append("topAttrs=\""); - sb.append(top); - sb.append("\"\n"); - } - - top = mTopLayoutAttributes.get(view); - if (top != null) { - // It's a layout attribute - sb.append(line.substring(0, classIndex)); // Indentation - - sb.append("topLayoutAttrs=\""); - sb.append(top); - sb.append("\"\n"); - } - } - } - } - - System.out.println("\nTop attributes:"); - System.out.println("--------------------------"); - List<String> views = new ArrayList<String>(mTopAttributes.keySet()); - Collections.sort(views); - for (String view : views) { - String top = mTopAttributes.get(view); - System.out.println(view + ": " + top); - } - - System.out.println("\nTop layout attributes:"); - System.out.println("--------------------------"); - views = new ArrayList<String>(mTopLayoutAttributes.keySet()); - Collections.sort(views); - for (String view : views) { - String top = mTopLayoutAttributes.get(view); - System.out.println(view + ": " + top); - } - - System.out.println("\nModified XML metadata file:\n"); - String newContent = sb.toString(); - File output = new File(mXmlMetadataFile.getParentFile(), mXmlMetadataFile.getName() + ".mod"); - if (output.exists()) { - output.delete(); - } - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(output)); - writer.write(newContent); - writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } - System.out.println("Done - wrote " + output.getPath()); - } - - //private File mPublicFile = new File(location, "data/res/values/public.xml"); - private File mPublicFile = new File("/Volumes/AndroidWork/git/frameworks/base/core/res/res/values/public.xml"); - - private void listAdvanced() { - Set<String> keys = new HashSet<String>(1000); - - // Merged usages across view types - Map<String, Usage> mergedUsages = new HashMap<String, Usage>(100); - - for (Entry<String,Map<String,Usage>> entry : mFrequencies.entrySet()) { - String view = entry.getKey(); - if (view.indexOf('.') != -1 && !view.startsWith("android.")) { - // Skip custom views etc - continue; - } - Map<String, Usage> map = entry.getValue(); - for (Usage usage : map.values()) { -// if (usage.count == 1) { -// System.out.println("Only found *one* usage of " + usage.attribute); -// } -// if (usage.count < 4) { -// System.out.println("Only found " + usage.count + " usage of " + usage.attribute); -// } - - String attribute = usage.attribute; - int index = attribute.indexOf(':'); - if (index == -1 || attribute.startsWith("android:")) { - Usage merged = mergedUsages.get(attribute); - if (merged == null) { - merged = new Usage(attribute); - merged.count = usage.count; - mergedUsages.put(attribute, merged); - } else { - merged.count += usage.count; - } - } - } - } - - for (Usage usage : mergedUsages.values()) { - String attribute = usage.attribute; - if (usage.count < 4) { - System.out.println("Only found " + usage.count + " usage of " + usage.attribute); - continue; - } - int index = attribute.indexOf(':'); - if (index != -1) { - attribute = attribute.substring(index + 1); // +1: skip ':' - } - keys.add(attribute); - } - - List<String> sorted = new ArrayList<String>(keys); - Collections.sort(sorted); - System.out.println("\nEncountered Attributes"); - System.out.println("-----------------------------"); - for (String attribute : sorted) { - System.out.println(attribute); - } - - System.out.println(); - } - - private static class Usage implements Comparable<Usage> { - public String attribute; - public int count; - - - public Usage(String attribute) { - super(); - this.attribute = attribute; - - count = 1; - } - - public void incrementCount() { - count++; - } - - @Override - public int compareTo(Usage o) { - // Sort by decreasing frequency, then sort alphabetically - int frequencyDelta = o.count - count; - if (frequencyDelta != 0) { - return frequencyDelta; - } else { - return attribute.compareTo(o.attribute); - } - } - - @Override - public String toString() { - return attribute + ": " + count; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((attribute == null) ? 0 : attribute.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Usage other = (Usage) obj; - if (attribute == null) { - if (other.attribute != null) - return false; - } else if (!attribute.equals(other.attribute)) - return false; - return true; - } - } -} |