com.google.gwt.soyc.MakeTopLevelHtmlForPerm Maven / Gradle / Ivy
/*
* 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.core.ext.soyc.impl.SizeMapRecorder;
import com.google.gwt.dev.util.Strings;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.collect.Sets;
import com.google.gwt.soyc.io.OutputDirectory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A utility to make all the HTML files for one permutation.
*/
public class MakeTopLevelHtmlForPerm {
/**
* A dependency linker for the initial code download. It links to the
* dependencies for the initial download.
*/
public class DependencyLinkerForInitialCode implements DependencyLinker {
@Override
public String dependencyLinkForClass(String className) {
String packageName = globalInformation.getClassToPackage().get(className);
assert packageName != null;
return dependenciesFileName("initial") + "#" + className;
}
}
/**
* A dependency linker for the leftovers fragment. It links to leftovers
* status pages.
*/
public class DependencyLinkerForLeftoversFragment implements DependencyLinker {
@Override
public String dependencyLinkForClass(String className) {
return leftoversStatusFileName() + "#"
+ hashedFilenameFragment(className);
}
}
/**
* A dependency linker for the total program breakdown. It links to a split
* status page.
*
*/
public class DependencyLinkerForTotalBreakdown implements DependencyLinker {
@Override
public String dependencyLinkForClass(String className) {
return splitStatusFileName() + "#" + hashedFilenameFragment(className);
}
}
/**
* A dependency linker that never links to anything.
*/
public static class NullDependencyLinker implements DependencyLinker {
@Override
public String dependencyLinkForClass(String className) {
return null;
}
}
interface DependencyLinker {
String dependencyLinkForClass(String className);
}
/**
* Use this class to intern strings to save space in the generated HTML. After
* populating the map, call getJs to create a JS array of all possible methods
* in this report.
*/
@SuppressWarnings("serial")
private class HtmlInterner {
// Hashes the interned string to the number of times this string is referenced.
Map builder = new HashMap();
// Hashes the interned string to its position in the final array of interned strings.
// Populated after the call to {@link #freeze()}
Map frozen = null;
/**
* Call this method after all calls to {@link #intern(String)} are complete.
* This routine then re-orders the interned calls in order of the number of
* times each string was referenced by intern() so that lower numbered index
* values represent more frequently referenced strings.
*/
public void freeze() {
final int maxDigits = 9;
assert (frozen == null);
assert (builder.size() < Math.pow(10, maxDigits));
// order the interned values with the most referenced first.
String[] temp = new String[builder.size()];
int index = 0;
for (String key : builder.keySet()) {
temp[index++] = key.format("%0" + maxDigits + "d%s", builder.get(key), key);
}
builder = null;
Arrays.sort(temp);
// strip off the numeric prefix on the key to build the frozen hash table
index = 0;
frozen = new LinkedHashMap();
for (int i = temp.length - 1; i >= 0; i--) {
frozen.put(temp[i].substring(maxDigits), index++);
}
}
/**
* Stores a string for later interning. Keeps track of the number of times a
* particular string is interned which will be used by {@link #freeze()} to
* place the most frequently used strings at the beginning of the
* dictionary. After a call to {@link #freeze()}, it is no longer valid to
* call this method.
*
* @param key string to be added to the intern dictionary.
*/
public void intern(String key) {
if (builder == null) {
throw new RuntimeException("freeze() already called.");
}
if (!builder.containsKey(key)) {
builder.put(key, 1);
} else {
int value = builder.get(key) + 1;
builder.put(key, value);
}
}
/**
* Displays a link for a split point that contains this code.
*/
public void printHasCodeInSplitPoint(PrintWriter outFile, String className,
int sp) {
outFile.print("h(" + frozen.get(getPackageSubstring(className)) + ","
+ frozen.get(getClassSubstring(className)) + "," + sp + ");");
}
/**
* Non specific message that there is code in an initial fragment.
*/
public void printHasInitialFragment(PrintWriter outFile) {
outFile.print("f();");
}
/**
* Displays a link for code in an initial fragment.
*/
public void printHasInitialFragment(PrintWriter outFile, String className) {
String packageName = getPackageSubstring(className);
outFile.print("g(" + frozen.get(packageName) + ","
+ frozen.get(getClassSubstring(className)) + ","
+ frozen.get(hashedFilenameFragment(packageName)) + ");");
}
public void printSomeCodeLeftover(PrintWriter outFile) {
outFile.print("i();");
}
/**
* Prints an h3 element with the class name and an anchor.
*/
private void printClassHeader(PrintWriter outFile, String className) {
outFile.print("e(" + frozen.get(getPackageSubstring(className)) + ","
+ frozen.get(getClassSubstring(className)) + ",'"
+ hashedFilenameFragment(className) + "');");
}
/**
* Print out a single class dependency stack in the methodDependencies
* report.
*/
private void printDependency(PrintWriter outFile,
Map dependencies, String method, String depMethod) {
String nameArray = "[" + frozen.get(getPackageSubstring(method)) + ","
+ frozen.get(getClassSubstring(method)) + ","
+ frozen.get(getMethodSubstring(method)) + "]";
outFile.print("b(" + nameArray + ",");
outFile.print("[");
while (depMethod != null) {
String nextDep = dependencies.get(depMethod);
// The bottom of the stack frame is not interesting.
if (nextDep != null) {
String packageString = getPackageSubstring(depMethod);
String classString = getClassSubstring(depMethod);
String methodString = getMethodSubstring(depMethod);
outFile.print("[" + frozen.get(packageString) + ","
+ frozen.get(classString) + "," + frozen.get(methodString) + "]");
}
depMethod = nextDep;
if (nextDep != null && dependencies.get(nextDep) != null) {
outFile.print(",");
}
}
outFile.print("]);");
}
/**
* Prints out a class header for the methodDependendies report.
*/
private void printDependencyClassHeader(PrintWriter outFile,
String className) {
outFile.print("a(" + frozen.get(getPackageSubstring(className)) + ","
+ frozen.get(getClassSubstring(className)) + ");");
}
/**
* Prints a JavaScript snippet that includes the dictionary of interned
* strings and methods to use interned strings to create lines in the
* report.
*
* Call this method after invoking {@link #freeze()}.
*
* @param outFile open file to write the data to.
*/
private void printInternedDataAsJs(PrintWriter outFile) {
if (frozen == null) {
throw new RuntimeException("freeze() not called.");
}
outFile.println(" var internedStrings = [");
for (String key : frozen.keySet()) {
outFile.print("\"" + key + "\",");
}
outFile.println("];");
// array of split point descriptions
outFile.println(" var spl = [");
for (int fragment = 1; fragment <= globalInformation.getNumFragments(); fragment++) {
final List fragmentDescriptors = globalInformation.getFragmentDescriptors(fragment);
String[] escapedFragmentDescriptors =
new String[fragmentDescriptors.size()];
for (int i = 0; i < fragmentDescriptors.size(); i++) {
escapedFragmentDescriptors[i] =
escapeJSString(fragmentDescriptors.get(i));
}
outFile.println(" '" + Strings.join(escapedFragmentDescriptors, ",")
+ "',");
}
outFile.println(" ];");
// object/dictionary containing method sizes
outFile.println(" var methodSizes = {");
for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) {
for (Entry methodEntry : breakdown.methodToSize.entrySet()) {
String methodSignature = methodEntry.getKey();
String method = methodSignature.substring(0, methodSignature.indexOf("("));
outFile.println(" \"" + method + "\" : " + methodEntry.getValue() + ",");
}
}
outFile.println("};");
// dropdown button image srcs
outFile.println(" var images = {");
outFile.println(" \"closed\" : \"images/play-g16.png\",");
outFile.println(" \"open\" : \"images/play-g16-down.png\",");
outFile.println(" };");
// TODO(zundel): Most of this below is just inserting a fixed string into the code. It would
// be easier to read and maintain if we could store the fixed part in a flat file. Use some
// kind of HTML template?
// function to print a class header in the methodDependencies report
// see printDependencyClassHeader()
outFile.println(" function a(packageRef, classRef) {");
outFile.println(" var className = internedStrings[packageRef] + \".\" + "
+ "internedStrings[classRef];");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\""
+ "Class: \" + className + \" \");");
outFile.println(" document.write(\"Size "
+ "(bytes) \");");
outFile.println(" document.write(\"\");");
outFile.println(" }");
outFile.println(" function swapShowHide(elementName) {");
outFile.println(" hp = document.getElementById(elementName);");
outFile.println(" arrow = document.getElementById(\"dropdown-\" + elementName);");
outFile.println(" if (hp.style.display !== \"none\" && hp.style.display "
+ "!== \"inline\") {");
outFile.println(" hp.style.display = \"inline\";");
outFile.println(" arrow.src = images[\"open\"];");
outFile.println(" } else if (hp.style.display === \"none\") {");
outFile.println(" hp.style.display = \"inline\";");
outFile.println(" arrow.src = images[\"open\"];");
outFile.println(" } else {");
outFile.println(" hp.style.display = \"none\";");
outFile.println(" arrow.src = images[\"closed\"];");
outFile.println(" }");
outFile.println(" }");
// function to print a single dependency in the methodDependencies report
// see printDependency()
outFile.println(" function b(c, deps) {");
outFile.println(" var methodName = internedStrings[c[0]] + \".\" + internedStrings[c[1]] "
+ "+ \"::\" + internedStrings[c[2]];");
outFile.println(" var methodSize = methodSizes[methodName];");
outFile.println(" if (methodSize === undefined) methodSize = \"--\";");
outFile.println(" var callstackId = \"callstack-\" + methodName;");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\"\" + methodName + \"\");");
outFile.println(" document.write(\"\");");
outFile.println(" for (var i = 0; i < deps.length ; i++) {");
outFile.println(" var s = deps[i];");
outFile.println(" document.write(\"- \" + internedStrings[s[0]] + \".\" + "
+ "internedStrings[s[1]] + \"::\" + internedStrings[s[2]] + \"
\");");
outFile.println(" }");
outFile.println(" document.write(\"
\");");
outFile.println(" document.write(\" \");");
outFile.println(" document.write(\"\" + "
+ "methodSize + \" \");");
outFile.println(" document.write(\" \");");
outFile.println(" }");
// follows all method dependency stacks
outFile.println(" function j() {");
outFile.println(" document.write(\"
\");");
outFile.println(" }");
// leftovers status line
outFile.println(" function c(packageRef,classRef,packageHashRef) {");
outFile.println(" var packageName = internedStrings[packageRef];");
outFile.println(" var className = packageName + \".\" + internedStrings[classRef];");
outFile.println(" var d1 = 'methodDependencies-total-" + getPermutationId() + ".html';");
outFile.println(" document.write(\"\");");
outFile.println(" document.write(\"- "
+ "See why it's live
\");");
outFile.println(" for (var sp = 1; sp <= "
+ globalInformation.getNumFragments() + "; sp++) {");
outFile.println(" var d2 = 'methodDependencies-sp' + sp + '-" + getPermutationId() + ".html';");
outFile.println(" document.write(\"- "
+ " See why it's not exclusive to s.p. #\" + sp + \" (\" + spl[sp - 1] + \")"
+ "
\");");
outFile.println(" }");
outFile.println(" document.write(\"
\");");
outFile.println(" }");
// leftovers status package header line
outFile.println(" function d(packageRef) {");
outFile.println(" document.write(\"Package: \" + "
+ "internedStrings[packageRef] + \"\");");
outFile.println(" }");
// leftovers status class header line
outFile.println(" function e(packageRef,classRef,classHashRef) {");
outFile.println(" document.write(\"\" + "
+ "internedStrings[packageRef] + \".\" + internedStrings[classRef] + \"
\");");
outFile.println(" }");
// split point has a class with code in the initial fragment - no link
outFile.println(" function f() {");
outFile.println(" document.write(\"Some code is included in the initial fragment"
+ "
\");");
outFile.println(" }");
// split point has a class with code in the initial fragment
outFile.println(" function g(packageRef, classRef, packageHashRef) {");
outFile.println(" document.write(\"Some code is included in the initial fragment "
+ "( See why)
\");");
outFile.println(" }");
// split point has code from class
outFile.println(" function h(packageRef, classRef, sp) {");
outFile.println(" document.write(\"Some code downloads with split point \" + sp + "
+ "\": \" + spl[sp - 1] + \"
\");");
outFile.println(" }");
// some code is left over
outFile.println(" function i() {");
outFile.println(" document.write(\"Some code is left over:
\");");
outFile.println(" }");
}
/**
* Prints links to each split point showing why a leftover fragment isn't
* exclusive.
*/
private void printLeftoversStatus(PrintWriter outFile, String packageName,
String className) {
outFile.println("c(" + frozen.get(packageName) + ","
+ frozen.get(getClassSubstring(className)) + ","
+ frozen.get(hashedFilenameFragment(packageName)) + ");");
}
/**
* Prints a div containing the package name in a blue block.
*/
private void printPackageHeader(PrintWriter outFile, String packageName) {
outFile.print("d(" + frozen.get(packageName) + ");");
}
}
/**
* By a convention shared with the compiler, the initial download is fragment
* number 0.
*/
private static final int FRAGMENT_NUMBER_INITIAL_DOWNLOAD = 0;
/**
* Just within this file, the convention is that the total program is fragment
* number -1.
*/
private static final int FRAGMENT_NUMBER_TOTAL_PROGRAM = -1;
/**
* A pattern describing the name of dependency graphs for code fragments
* corresponding to a specific split point. These can be either exclusive
* fragments or fragments of code for split points in the initial load
* sequence.
*/
private static final Pattern PATTERN_SP_INT = Pattern.compile("sp([0-9]+)");
public static void makeTopLevelHtmlForAllPerms(
Map> allPermsInfo, OutputDirectory outDir)
throws IOException {
PrintWriter outFile = new PrintWriter(outDir.getOutputStream("index.html"));
addStandardHtmlProlog(outFile, "Compile report", "Compile report",
"Overview of permutations");
outFile.println("");
// in order to print these in ascending order, we have to sort by
// integers
SortedSet sortedPermIds = new TreeSet();
for (String permutationId : allPermsInfo.keySet()) {
sortedPermIds.add(Integer.parseInt(permutationId));
}
for (Integer sortedPermId : sortedPermIds) {
String permutationId = Integer.toString(sortedPermId);
List permutationInfoList = allPermsInfo.get(permutationId);
outFile.print("- Permutation " + permutationId);
for (String desc : permutationInfoList) {
outFile.println(" (" + desc + ")");
}
outFile.println("
");
outFile.println("- ");
outFile.println("Split Point Report");
outFile.println("
");
outFile.println("- ");
outFile.println("Compiler Metrics");
outFile.println("
");
outFile.println("
");
outFile.println(" ");
}
outFile.println("
");
addStandardHtmlEnding(outFile);
outFile.close();
}
/**
* @return given "com.foo.myClass" or "com.foo.myClass::myMethod" returns
* "myClass"
*/
static String getClassSubstring(String fullMethodName) {
if (fullMethodName.length() == 0) {
return "";
}
int startIndex = getPackageSubstring(fullMethodName).length() + 1;
int endIndex = fullMethodName.indexOf("::");
if (endIndex == -1) {
endIndex = fullMethodName.length();
}
if (startIndex > endIndex || startIndex > fullMethodName.length()) {
return "";
}
return fullMethodName.substring(startIndex, endIndex);
}
/**
* @return given "com.foo.myClass::myMethod" returns "myMethod"
*/
static String getMethodSubstring(String fullMethodName) {
int index = fullMethodName.indexOf("::");
if (index == -1) {
return "";
}
index += 2;
if (index >= fullMethodName.length()) {
return "";
}
return fullMethodName.substring(index);
}
/**
* @return given "com.foo.myClass" or "com.foo.myClass::myMethod" returns
* "com.foo"
*/
static String getPackageSubstring(String fullMethodName) {
int endIndex = fullMethodName.lastIndexOf('.');
if (endIndex == -1) {
endIndex = fullMethodName.length();
}
return fullMethodName.substring(0, endIndex);
}
private static void addSmallHtmlProlog(final PrintWriter outFile, String title) {
outFile.println("");
outFile.println("");
outFile.println("");
outFile.println("");
outFile.println(title);
outFile.println(" ");
outFile.println("");
outFile.println("");
}
private static void addStandardHtmlEnding(final PrintWriter out) {
out.println("