From e287bc8580e19d72a911615475b353790cf000eb Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Mon, 15 Aug 2011 16:38:30 -0700 Subject: Layout editor property menu improvements This changeset adds two forms of view attribute metadata: * First, it records the most commonly used attributes for each view. This was determined by gathering statistics on as many layout files as I could find and then picking those that are used 10% or more. * Second, it records in the attribute metadata which View defines a given attribute. The context menu uses the above information to present the available attributes in several ways: * In the top level menu, where we had "Edit ID", and if applicable "Edit Text", it now lists the top attributes instead. For example, for a RatingBar the first handful of menu options are "Edit ID...", "Edit NumStars...", "Edit StepSize...", "Edit Style..." and "IsIndicator" (a boolean pull-right menu). Incidentally this automatically handles some cases which were manually handled before, so the code in LinearLayoutRule to add an "Orientation" menu is no longer needed; it's just one of the two common attributes handled by the new attribute list. * The "Properties" menu is now called "Other Properties", and instead of showing all properties, it has a new level of menus: * "Recent". This is initially empty, but as you edit other attributes, it gets populated (in most recently used order, kept up to date) with recently edited properties. * One submenu for each defining View super class listing exactly the attributes defined by that view. This is useful for browsing and editing related attributes. If you are looking at a textual view like a Button for example, you can look at the "TextView" menu to find all the text related options (TextColor, TextSize, etc). These menus are listed from the nearest to the further superclass, so for example if you right click on a CalendarView you'll see these menus: Recent > ---------------------------- Defined by CalendarView > Inherited from FrameLayout > Inherited from ViewGroup > Inherited from View > ---------------------------- Layout Parameters > ---------------------------- All By Name > * As you can see from the above, there are two more menus below the inherited menu items. "Layout Parameters" lists all the layout parameters available for the selected nodes (which is defined not by the view itself but the view that it is contained within). And finally there is "All By Name", which is a complete menu containing all available attributes for the view (and this is what the Properties menu used to contain). * The code which computes a display name from an attribute was also tweaked to capitalize not just the first letter but any first word letter, so for example when you look at the possible values for Gravity you now see "Clip Vertical" instead of "Clip vertical". * The edit property dialog for the properties menus now uses @string or @style resource choosers for the text, hint and style attributes (used to just be a plain text box.) Change-Id: I3b30d48b85fd13f0190c760756bf383a47b3f4a5 --- attribute_stats/.classpath | 6 + attribute_stats/.gitignore | 1 + attribute_stats/.project | 17 + .../.settings/org.eclipse.jdt.core.prefs | 71 +++ attribute_stats/README.txt | 13 + attribute_stats/src/Analyzer.java | 582 +++++++++++++++++++++ 6 files changed, 690 insertions(+) create mode 100644 attribute_stats/.classpath create mode 100644 attribute_stats/.gitignore create mode 100644 attribute_stats/.project create mode 100644 attribute_stats/.settings/org.eclipse.jdt.core.prefs create mode 100644 attribute_stats/README.txt create mode 100644 attribute_stats/src/Analyzer.java (limited to 'attribute_stats') diff --git a/attribute_stats/.classpath b/attribute_stats/.classpath new file mode 100644 index 0000000..fb50116 --- /dev/null +++ b/attribute_stats/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/attribute_stats/.gitignore b/attribute_stats/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/attribute_stats/.gitignore @@ -0,0 +1 @@ +bin diff --git a/attribute_stats/.project b/attribute_stats/.project new file mode 100644 index 0000000..2f2cff1 --- /dev/null +++ b/attribute_stats/.project @@ -0,0 +1,17 @@ + + + attribute_stats + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/attribute_stats/.settings/org.eclipse.jdt.core.prefs b/attribute_stats/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..e755df2 --- /dev/null +++ b/attribute_stats/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,71 @@ +#Thu Jun 09 12:26:44 PDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/attribute_stats/README.txt b/attribute_stats/README.txt new file mode 100644 index 0000000..15f51c2 --- /dev/null +++ b/attribute_stats/README.txt @@ -0,0 +1,13 @@ +Attribute Statistics +--------------------- + +This program gathers statistics about attribute usage in layout +files. This is how the "topAttrs" attributes listed in ADT's +extra-view-metadata.xml file (which drives the common attributes +listed in the top of the context menu) is determined by running this +script on a body of sample Android code, such as the AOSP repository. + +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. diff --git a/attribute_stats/src/Analyzer.java b/attribute_stats/src/Analyzer.java new file mode 100644 index 0000000..a6bbb4a --- /dev/null +++ b/attribute_stats/src/Analyzer.java @@ -0,0 +1,582 @@ +/* + * 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.List; +import java.util.Map; + +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. + *

+ * 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 mDirectories; + private File mCurrentFile; + + /** Map from view id to map from attribute to frequency count */ + private Map> mFrequencies = + new HashMap>(100); + + private Map> mLayoutAttributeFrequencies = + new HashMap>(100); + + private Map mTopAttributes = new HashMap(100); + private Map mTopLayoutAttributes = new HashMap(100); + + private int mFileVisitCount; + private int mLayoutFileCount; + private File mXmlMetadataFile; + + private Analyzer(List directories, File xmlMetadataFile) { + mDirectories = directories; + mXmlMetadataFile = xmlMetadataFile; + } + + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: " + Analyzer.class.getSimpleName() + + " [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 directories = new ArrayList(); + for (int i = 0, n = args.length; i < n; i++) { + String arg = args[i]; + + // 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).analyze(); + } + + private void analyze() { + for (File directory : mDirectories) { + scanDirectory(directory); + } + 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 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 attributeMap = mFrequencies.get(tag); + if (attributeMap == null) { + attributeMap = new HashMap(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 attributeMap = mLayoutAttributeFrequencies.get(parentTag); + if (attributeMap == null) { + attributeMap = new HashMap(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 views = new ArrayList(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(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 map) { + if (map == null) { + return null; + } + + if (view.indexOf('.') != -1 && !view.startsWith("android.")) { + // Skip custom views + return null; + } + + List values = new ArrayList(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 views = new ArrayList(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(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 static class Usage implements Comparable { + public String attribute; + public int count; + + + public Usage(String attribute) { + super(); + this.attribute = attribute; + + count = 1; + } + + public void incrementCount() { + count++; + } + + 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; + } + } +} -- cgit v1.1