| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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. |
| */ |
| |
| package com.google.gwt.soyc; |
| |
| import com.google.gwt.core.ext.linker.CompilationMetricsArtifact; |
| import com.google.gwt.core.ext.linker.ModuleMetricsArtifact; |
| import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact; |
| import com.google.gwt.dev.util.StringInterner; |
| import com.google.gwt.soyc.MakeTopLevelHtmlForPerm.DependencyLinker; |
| import com.google.gwt.soyc.MakeTopLevelHtmlForPerm.NullDependencyLinker; |
| import com.google.gwt.soyc.io.OutputDirectory; |
| |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| |
| /** |
| * The command-line entry point for creating a compile report. |
| */ |
| public class SoycDashboard { |
| private static class FormatException extends RuntimeException { |
| public FormatException() { |
| super(); |
| } |
| |
| public FormatException(String message) { |
| super(message); |
| } |
| |
| public FormatException(Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| /* |
| * cleans up the RPC code categories |
| */ |
| private static void foldInRPCHeuristic( |
| final HashMap<String, CodeCollection> nameToCodeColl) { |
| /** |
| * Heuristic: this moves all classes that override serializable from RPC to |
| * "Other Code" *if* there is no RPC generated code, i.e., if the |
| * application really is not using RPC |
| */ |
| |
| if (nameToCodeColl.get("rpcGen").classes.size() == 0) { |
| |
| for (String className : nameToCodeColl.get("rpcUser").classes) { |
| |
| if ((!nameToCodeColl.get("widget").classes.contains(className)) |
| && (!nameToCodeColl.get("jre").classes.contains(className)) |
| && (!nameToCodeColl.get("gwtLang").classes.contains(className))) { |
| nameToCodeColl.get("allOther").classes.add(className); |
| } |
| } |
| nameToCodeColl.get("rpcUser").classes.clear(); |
| |
| for (String className : nameToCodeColl.get("rpcGwt").classes) { |
| if ((!nameToCodeColl.get("widget").classes.contains(className)) |
| && (!nameToCodeColl.get("jre").classes.contains(className)) |
| && (!nameToCodeColl.get("gwtLang").classes.contains(className))) { |
| nameToCodeColl.get("allOther").classes.add(className); |
| } |
| } |
| nameToCodeColl.get("rpcGwt").classes.clear(); |
| } |
| } |
| |
| private static DefaultHandler parseXMLDocumentDependencies( |
| final Map<String, Map<String, String>> allDependencies) { |
| DefaultHandler handler = new DefaultHandler() { |
| |
| // may want to create a class for this later |
| String curMethod; |
| Map<String, String> dependencies = new TreeMap<String, String>(); |
| String graphExtends = null; |
| StringBuilder valueBuilder = new StringBuilder(); |
| |
| @Override |
| public void endElement(String uri, String localName, String qName) { |
| if (localName.compareTo("table") == 0) { |
| if (graphExtends != null) { |
| // Add in elements from the extended graph |
| for (Entry<String, String> entry : allDependencies.get(graphExtends).entrySet()) { |
| dependencies.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void startElement(String nsUri, String strippedName, |
| String tagName, final Attributes attributes) { |
| |
| valueBuilder.delete(0, valueBuilder.length()); |
| |
| if (strippedName.compareTo("table") == 0 |
| && (attributes.getValue("name") != null)) { |
| String name = attributes.getValue("name"); |
| dependencies = new TreeMap<String, String>(); |
| allDependencies.put(StringInterner.get().intern(name), dependencies); |
| if (attributes.getValue("extends") != null) { |
| graphExtends = StringInterner.get().intern(attributes.getValue("extends")); |
| if (!allDependencies.containsKey(graphExtends)) { |
| throw new FormatException("Graph " + name |
| + " extends an unknown graph " + graphExtends); |
| } |
| } else { |
| graphExtends = null; |
| } |
| } else if ((strippedName.compareTo("method") == 0) |
| && (attributes.getValue("name") != null)) { |
| curMethod = StringInterner.get().intern(attributes.getValue("name")); |
| } else if ((strippedName.compareTo("called") == 0) |
| && (attributes.getValue("by") != null)) { |
| String curDepMethod = attributes.getValue("by"); |
| if (!dependencies.containsKey(curMethod)) { |
| dependencies.put(StringInterner.get().intern(curMethod), |
| StringInterner.get().intern(curDepMethod)); |
| } |
| } |
| } |
| }; |
| return handler; |
| } |
| |
| /** |
| * Global information for the current permutation being emitted. |
| */ |
| private GlobalInformation globalInformation; |
| |
| /** |
| * HTML emitter for the current permutation being emitted. |
| */ |
| private MakeTopLevelHtmlForPerm makeTopLevelHtmlForPerm; |
| |
| private final OutputDirectory outDir; |
| |
| public SoycDashboard(OutputDirectory outDir) { |
| this.outDir = outDir; |
| } |
| |
| public void generateCompilerMetricsForOnePermutation( |
| ModuleMetricsArtifact moduleMetrics, |
| PrecompilationMetricsArtifact precompilationMetrics, |
| CompilationMetricsArtifact compilationMetrics) throws IOException { |
| makeTopLevelHtmlForPerm.makeCompilerMetricsPermFiles(moduleMetrics, |
| precompilationMetrics, compilationMetrics); |
| } |
| |
| public void generateCrossPermutationFiles(Map<String, List<String>> permInfo) |
| throws IOException { |
| StaticResources.emit(outDir); |
| MakeTopLevelHtmlForPerm.makeTopLevelHtmlForAllPerms(permInfo, outDir); |
| } |
| |
| public void generateForOnePermutation() throws IOException { |
| if (globalInformation.dependencies != null) { |
| makeTopLevelHtmlForPerm.makeDependenciesHtml(); |
| } |
| |
| if (globalInformation.getNumFragments() > 0) { |
| makeTopLevelHtmlForPerm.makeSplitStatusPages(); |
| makeTopLevelHtmlForPerm.makeLeftoverStatusPages(); |
| } |
| for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) { |
| DependencyLinker linker = chooseDependencyLinker(breakdown); |
| makeHTMLFiles(makeTopLevelHtmlForPerm, breakdown, linker); |
| } |
| } |
| |
| public void readDependencies(InputStream stream) |
| throws ParserConfigurationException, SAXException, IOException { |
| globalInformation.dependencies = new TreeMap<String, Map<String, String>>(); |
| DefaultHandler depHandler = parseXMLDocumentDependencies(globalInformation.dependencies); |
| SAXParserFactory depFactoryMain = SAXParserFactory.newInstance(); |
| depFactoryMain.setNamespaceAware(true); |
| SAXParser saxParser = depFactoryMain.newSAXParser(); |
| saxParser.parse(stream, depHandler); |
| } |
| |
| public void readSizeMaps(InputStream stream) |
| throws ParserConfigurationException, SAXException, IOException { |
| DefaultHandler handler = parseXMLDocumentSizeMap(globalInformation); |
| SAXParserFactory factoryMain = SAXParserFactory.newInstance(); |
| factoryMain.setNamespaceAware(true); |
| SAXParser saxParser = factoryMain.newSAXParser(); |
| saxParser.parse(stream, handler); |
| |
| // Now clean up the information that has been read in various ways |
| globalInformation.computePackageSizes(); |
| |
| // add to "All Other Code" if none of the special categories apply |
| for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) { |
| updateAllOtherCodeType(breakdown.nameToCodeColl); |
| } |
| |
| // clean up the RPC categories |
| for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) { |
| foldInRPCHeuristic(breakdown.nameToCodeColl); |
| } |
| } |
| |
| public void readSplitPoints(InputStream stream) |
| throws ParserConfigurationException, SAXException, IOException { |
| DefaultHandler splitPointHandler = parseXMLDocumentSplitPoints(); |
| SAXParserFactory splitPointsFactoryMain = SAXParserFactory.newInstance(); |
| splitPointsFactoryMain.setNamespaceAware(true); |
| |
| SAXParser saxParser = splitPointsFactoryMain.newSAXParser(); |
| saxParser.parse(stream, splitPointHandler); |
| } |
| |
| public void startNewPermutation(String permutationId) { |
| globalInformation = new GlobalInformation(permutationId); |
| makeTopLevelHtmlForPerm = new MakeTopLevelHtmlForPerm(globalInformation, |
| outDir); |
| } |
| |
| private Collection<SizeBreakdown> breakdownsForFragment(Integer fragment) { |
| List<SizeBreakdown> breakdowns = new ArrayList<SizeBreakdown>(); |
| breakdowns.add(globalInformation.getTotalCodeBreakdown()); |
| if (fragment == 0) { |
| breakdowns.add(globalInformation.getInitialCodeBreakdown()); |
| } |
| if (fragment == (globalInformation.getNumFragments() + 1)) { |
| breakdowns.add(globalInformation.getLeftoversBreakdown()); |
| } |
| if (fragment >= 1 && fragment <= globalInformation.getNumFragments()) { |
| breakdowns.add(globalInformation.fragmentCodeBreakdown(fragment)); |
| } |
| return breakdowns; |
| } |
| |
| private DependencyLinker chooseDependencyLinker(SizeBreakdown breakdown) { |
| if (globalInformation.dependencies == null) { |
| // no dependencies are available |
| return new NullDependencyLinker(); |
| } |
| |
| if (breakdown == globalInformation.getTotalCodeBreakdown()) { |
| if (globalInformation.getNumFragments() > 0) { |
| return makeTopLevelHtmlForPerm.new DependencyLinkerForTotalBreakdown(); |
| } else { |
| return makeTopLevelHtmlForPerm.new DependencyLinkerForInitialCode(); |
| } |
| } else if (breakdown == globalInformation.getInitialCodeBreakdown()) { |
| return makeTopLevelHtmlForPerm.new DependencyLinkerForInitialCode(); |
| } else if (breakdown == globalInformation.getLeftoversBreakdown()) { |
| assert globalInformation.getNumFragments() > 0; |
| return makeTopLevelHtmlForPerm.new DependencyLinkerForLeftoversFragment(); |
| } else { |
| return new NullDependencyLinker(); |
| } |
| } |
| |
| /** |
| * Generates all the HTML files for one size breakdown. |
| */ |
| private void makeHTMLFiles(MakeTopLevelHtmlForPerm makeTopLevelHtmlForPerm, |
| SizeBreakdown breakdown, DependencyLinker depLinker) throws IOException { |
| makeTopLevelHtmlForPerm.makePackageClassesHtmls(breakdown, depLinker); |
| makeTopLevelHtmlForPerm.makeCodeTypeClassesHtmls(breakdown); |
| makeTopLevelHtmlForPerm.makeLiteralsClassesTableHtmls(breakdown); |
| makeTopLevelHtmlForPerm.makeBreakdownShell(breakdown); |
| makeTopLevelHtmlForPerm.makeTopLevelShell(); |
| } |
| |
| private DefaultHandler parseXMLDocumentSizeMap( |
| final GlobalInformation globalInformation) { |
| return new DefaultHandler() { |
| int fragment = -1; |
| |
| @Override |
| public void endElement(String uri, String localName, String qName) { |
| if (localName.compareTo("sizemap") == 0) { |
| fragment = -1; |
| } |
| } |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, |
| final Attributes attributes) { |
| if (localName.compareTo("sizemap") == 0) { |
| // starting a new size map |
| String fragString = attributes.getValue("fragment"); |
| if (fragString == null) { |
| throw new FormatException(); |
| } |
| try { |
| fragment = Integer.valueOf(fragString); |
| } catch (NumberFormatException e) { |
| throw new FormatException(e); |
| } |
| String sizeString = attributes.getValue("size"); |
| if (sizeString == null) { |
| throw new FormatException(); |
| } |
| int size; |
| try { |
| size = Integer.valueOf(sizeString); |
| } catch (NumberFormatException e) { |
| throw new FormatException(e); |
| } |
| for (SizeBreakdown breakdown : breakdownsForFragment(fragment)) { |
| breakdown.sizeAllCode += size; |
| } |
| } else if (localName.compareTo("size") == 0) { |
| String type = attributes.getValue("type"); |
| if (type == null) { |
| throw new FormatException(); |
| } |
| String ref = attributes.getValue("ref"); |
| if (ref == null) { |
| throw new FormatException(); |
| } |
| String sizeString = attributes.getValue("size"); |
| if (sizeString == null) { |
| throw new FormatException(); |
| } |
| int size; |
| try { |
| size = Integer.valueOf(sizeString); |
| } catch (NumberFormatException e) { |
| throw new FormatException(e); |
| } |
| recordSize(type, ref, size, globalInformation); |
| } |
| } |
| |
| private void accountForSize(SizeBreakdown breakdown, String refType, |
| String ref, int size, GlobalInformation globalInformation) { |
| if (refType.equals("string")) { |
| LiteralsCollection stringLiterals = breakdown.nameToLitColl.get("string"); |
| stringLiterals.size += size; |
| stringLiterals.literals.add(ref); |
| } else if (refType.equals("var")) { |
| // Nothing to record, because no breakdown is provided for random |
| // variables |
| } else { |
| if (!refType.equals("type") && !refType.equals("method") && !refType.equals("field")) { |
| throw new FormatException(); |
| } |
| |
| if (refType.equals("method")) { |
| breakdown.methodToSize.put(ref, Integer.valueOf(size)); |
| } |
| |
| String className = ref; |
| if (className.contains("::")) { |
| /* |
| * It's a method or field reference. Discard the method/field part. |
| */ |
| int idx = className.indexOf(':'); |
| className = className.substring(0, idx); |
| } |
| className = StringInterner.get().intern(className); |
| // derive the package name from the class |
| String packageName; |
| if (!globalInformation.getClassToPackage().containsKey(className)) { |
| packageName = className; |
| packageName = packageName.replaceAll("\\.[A-Z].*", ""); |
| globalInformation.getClassToPackage().put(className, packageName); |
| } else { |
| packageName = globalInformation.getClassToPackage().get(className); |
| } |
| |
| if (!globalInformation.getPackageToClasses().containsKey(packageName)) { |
| TreeSet<String> insertSet = new TreeSet<String>(); |
| insertSet.add(className); |
| globalInformation.getPackageToClasses().put(packageName, insertSet); |
| } else { |
| globalInformation.getPackageToClasses().get(packageName).add( |
| className); |
| } |
| |
| recordClassCategories(breakdown.nameToCodeColl, className, |
| packageName); |
| |
| if (breakdown.classToSize.containsKey(className)) { |
| int newSize = breakdown.classToSize.get(className) + size; |
| breakdown.classToSize.put(className, newSize); |
| } else { |
| breakdown.classToSize.put(className, size); |
| } |
| } |
| } |
| |
| private void recordClassCategories( |
| final HashMap<String, CodeCollection> nameToCodeColl, |
| String className, String packageName) { |
| if (packageName.startsWith("java")) { |
| nameToCodeColl.get("jre").classes.add(className); |
| } else if (packageName.startsWith("com.google.gwt.lang")) { |
| nameToCodeColl.get("gwtLang").classes.add(className); |
| } |
| if (className.contains("_CustomFieldSerializer")) { |
| nameToCodeColl.get("rpcUser").classes.add(className); |
| } else if (className.endsWith("_FieldSerializer") |
| || className.endsWith("_Proxy") |
| || className.endsWith("_TypeSerializer")) { |
| nameToCodeColl.get("rpcGen").classes.add(className); |
| } |
| } |
| |
| private void recordSize(String refType, String ref, int size, |
| GlobalInformation globalInformation) { |
| refType = StringInterner.get().intern(refType); |
| ref = StringInterner.get().intern(ref); |
| for (SizeBreakdown breakdown : breakdownsForFragment(fragment)) { |
| accountForSize(breakdown, refType, ref, size, globalInformation); |
| } |
| } |
| }; |
| } |
| |
| private DefaultHandler parseXMLDocumentSplitPoints() { |
| DefaultHandler handler = new DefaultHandler() { |
| |
| private boolean inInitialLoadSequence = false; |
| |
| @Override |
| public void endElement(String uri, String localName, String qName) { |
| if (localName.compareTo("initialesq") == 0) { |
| inInitialLoadSequence = false; |
| } |
| } |
| |
| @Override |
| public void startElement(String nsUri, String strippedName, |
| String tagName, final Attributes attributes) { |
| if (strippedName.compareTo("splitpoint") == 0) { |
| parseSplitPoint(attributes); |
| } else if (strippedName.compareTo("initialseq") == 0) { |
| inInitialLoadSequence = true; |
| } else if (inInitialLoadSequence |
| && strippedName.compareTo("splitpointref") == 0) { |
| globalInformation.getInitialFragmentLoadSequence().add( |
| parseSplitPointReference(attributes)); |
| } |
| } |
| |
| /** |
| * Parses a split point entry from a splitpoints XML soyc file. |
| * A split point node as in \<splitpoint id=N location=DESC/\> |
| * |
| * @param attributes the attributes of the splitpoint node (provided by the SAX parsing |
| * infrastructure) |
| */ |
| private void parseSplitPoint(final Attributes attributes) { |
| if (attributes.getValue("id") != null) { |
| String curSplitPoint = attributes.getValue("id"); |
| if (attributes.getValue("location") != null) { |
| String curSplitPointLocation = attributes.getValue("location"); |
| |
| curSplitPointLocation = curSplitPointLocation.replaceAll("\\(L.*", |
| ""); |
| |
| globalInformation.addFragmentDescriptor( |
| Integer.parseInt(curSplitPoint), curSplitPointLocation); |
| } |
| } |
| } |
| |
| private Integer parseSplitPointReference(final Attributes attributes) { |
| String spString = attributes.getValue("id"); |
| if (spString == null) { |
| throw new FormatException("Could not parse split point reference"); |
| } |
| return Integer.valueOf(spString); |
| } |
| |
| }; |
| return handler; |
| } |
| |
| /* |
| * assigns code to "all other code" if none of the special categories apply |
| */ |
| private void updateAllOtherCodeType( |
| final HashMap<String, CodeCollection> nameToCodeColl) { |
| // all classes not in any of the other categories |
| for (String className : globalInformation.getClassToPackage().keySet()) { |
| if ((!nameToCodeColl.get("widget").classes.contains(className)) |
| && (!nameToCodeColl.get("rpcUser").classes.contains(className)) |
| && (!nameToCodeColl.get("rpcGwt").classes.contains(className)) |
| && (!nameToCodeColl.get("rpcGen").classes.contains(className)) |
| && (!nameToCodeColl.get("jre").classes.contains(className)) |
| && (!nameToCodeColl.get("gwtLang").classes.contains(className))) { |
| nameToCodeColl.get("allOther").classes.add(className); |
| } |
| } |
| } |
| } |