Prepends the proper amount of indentation on code snippets written in to the benchmark reports.
Pair programmed.
Changes by: bruce, toby
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@952 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 99ba8ed..11a52cb 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -956,6 +956,18 @@
return true;
}
+ /**
+ * Escapes '&', '<', '>', '"', and '\'' to their XML entity equivalents.
+ */
+ private static String escapeXml(String unescaped) {
+ String escaped = unescaped.replaceAll("\\&", "&");
+ escaped = escaped.replaceAll("\\<", "<");
+ escaped = escaped.replaceAll("\\>", ">");
+ escaped = escaped.replaceAll("\\\"", """);
+ escaped = escaped.replaceAll("\\'", "'");
+ return escaped;
+ }
+
private static void writeAttribute(PrintWriter w, Attr attr, int depth)
throws IOException {
w.write(attr.getName());
@@ -1053,7 +1065,9 @@
}
private static void writeText(PrintWriter w, Text text) throws DOMException {
- w.write(text.getNodeValue());
+ String nodeValue = text.getNodeValue();
+ String escaped = escapeXml(nodeValue);
+ w.write(escaped);
}
/**
diff --git a/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java b/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java
index ffbe2b9..8e8fd7f 100644
--- a/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java
+++ b/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java
@@ -1,12 +1,12 @@
/*
* Copyright 2007 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
@@ -15,62 +15,57 @@
*/
package com.google.gwt.junit.benchmarks;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.HasMetaData;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.junit.rebind.BenchmarkGenerator;
+import com.google.gwt.dev.util.Util;
import com.google.gwt.junit.client.TestResults;
import com.google.gwt.junit.client.Trial;
+import com.google.gwt.junit.rebind.BenchmarkGenerator;
+import com.google.gwt.util.tools.Utility;
import junit.framework.TestCase;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
+import org.eclipse.jdt.internal.compiler.SourceElementParser;
+import org.eclipse.jdt.internal.compiler.SourceElementRequestorAdapter;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
-import org.eclipse.jdt.internal.compiler.IProblemFactory;
-import org.eclipse.jdt.internal.compiler.SourceElementParser;
-import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
-import org.eclipse.jdt.internal.compiler.SourceElementRequestorAdapter;
-import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
-import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
-import java.io.IOException;
-import java.io.FileOutputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileReader;
import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.text.BreakIterator;
+import java.text.DateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.List;
-import java.util.ArrayList;
import java.util.Locale;
-import java.util.regex.Pattern;
+import java.util.Map;
import java.util.regex.Matcher;
-import java.text.DateFormat;
-import java.text.BreakIterator;
+import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.dom.DOMSource;
/**
* Generates a detailed report that contains the results of all of the
* benchmark-related unit tests executed during a unit test session. The primary
* user of this class is JUnitShell.
- *
+ *
* The report is in XML format. To view the XML reports, use benchmarkViewer.
- *
*/
public class BenchmarkReport {
@@ -81,17 +76,15 @@
private MetaData metaData;
- private List/*<JUnitMessageQueue.TestResult>*/ results;
+ private List/* <JUnitMessageQueue.TestResult> */results;
private TestCase test;
BenchmarkXml(TestCase test,
- List/*<JUnitMessageQueue.TestResult>*/ results) {
+ List/* <JUnitMessageQueue.TestResult> */results) {
this.test = test;
this.results = results;
- Map/*<String,MetaData>*/ methodMetaData
- = (Map/*<String,MetaData>*/) testMetaData
- .get(test.getClass().toString());
+ Map/* <String,MetaData> */methodMetaData = (Map/* <String,MetaData> */) testMetaData.get(test.getClass().toString());
metaData = (MetaData) methodMetaData.get(test.getName());
}
@@ -149,8 +142,8 @@
trialElement.appendChild(variableElement);
}
- trialElement
- .setAttribute("timing", String.valueOf(trial.getRunTimeMillis()));
+ trialElement.setAttribute("timing",
+ String.valueOf(trial.getRunTimeMillis()));
Throwable exception = trial.getException();
@@ -166,11 +159,11 @@
/**
* Parses a .java source file to get the source code for methods.
- *
+ *
* This Parser takes some shortcuts based on the fact that it's only being
* used to locate test methods for unit tests. (For example, only requiring a
* method name instead of a full type signature for lookup).
- *
+ *
* TODO(tobyr) I think that I might be able to replace all this code with a
* call to the existing metadata interface. Check declEnd/declStart in
* JAbstractMethod.
@@ -179,7 +172,7 @@
static class MethodBody {
- int declarationEnd; // the character index of the end of the method
+ int declarationEnd; // the character index of the end of the method
int declarationStart; // the character index of the start of the method
@@ -189,8 +182,7 @@
private MethodBody currentMethod; // Only used during the visitor
// But it's less painful
- private Map/*<String,MethodBody>*/ methods
- = new HashMap/*<String,MethodBody>*/();
+ private Map/* <String,MethodBody> */methods = new HashMap/* <String,MethodBody> */();
// Contains the contents of the entire source file
private char[] sourceContents;
@@ -245,22 +237,17 @@
/**
* Returns the source code for the method of the given name.
- *
+ *
* @return null if the source code for the method can not be located
*/
public String getMethod(JMethod method) {
/*
- MethodBody methodBody = (MethodBody)methods.get( method.getName() );
- if ( methodBody == null ) {
- return null;
- }
- if ( methodBody.source == null ) {
- methodBody.source = new String(sourceContents,
- methodBody.declarationStart, methodBody.declarationEnd - methodBody.
- declarationStart + 1);
- }
- return methodBody.source;
- */
+ * MethodBody methodBody = (MethodBody)methods.get( method.getName() ); if (
+ * methodBody == null ) { return null; } if ( methodBody.source == null ) {
+ * methodBody.source = new String(sourceContents,
+ * methodBody.declarationStart, methodBody.declarationEnd - methodBody.
+ * declarationStart + 1); } return methodBody.source;
+ */
return new String(sourceContents, method.getDeclStart(),
method.getDeclEnd() - method.getDeclStart() + 1);
}
@@ -271,21 +258,40 @@
*/
private class ReportXml {
- private Map/*<String,Element>*/ categoryElementMap
- = new HashMap/*<String,Element>*/();
+ private Map/* <String,Element> */categoryElementMap = new HashMap/* <String,Element> */();
private Date date = new Date();
private String version = "unknown";
+ Element toElement(Document doc) {
+ Element report = doc.createElement("gwt_benchmark_report");
+ String dateString = DateFormat.getDateTimeInstance().format(date);
+ report.setAttribute("date", dateString);
+ report.setAttribute("gwt_version", version);
+
+ // - Add each test result into the report.
+ // - Add the category for the test result, if necessary.
+ for (Iterator entryIt = testResults.entrySet().iterator(); entryIt.hasNext();) {
+ Map.Entry entry = (Map.Entry) entryIt.next();
+ TestCase test = (TestCase) entry.getKey();
+ List/* <JUnitMessageQueue.TestResult> */results = (List/* <JUnitMessageQueue.TestResult> */) entry.getValue();
+ BenchmarkXml xml = new BenchmarkXml(test, results);
+ Element categoryElement = getCategoryElement(doc, report,
+ xml.metaData.getCategory().getClassName());
+ categoryElement.appendChild(xml.toElement(doc));
+ }
+
+ return report;
+ }
+
/**
* Locates or creates the category element by the specified name.
- *
+ *
* @param doc The document to search
* @return The matching category element
*/
- private Element getCategoryElement(Document doc, Element report,
- String name) {
+ private Element getCategoryElement(Document doc, Element report, String name) {
Element e = (Element) categoryElementMap.get(name);
if (e != null) {
@@ -302,42 +308,18 @@
return categoryElement;
}
-
- Element toElement(Document doc) {
- Element report = doc.createElement("gwt_benchmark_report");
- String dateString = DateFormat.getDateTimeInstance().format(date);
- report.setAttribute("date", dateString);
- report.setAttribute("gwt_version", version);
-
- // - Add each test result into the report.
- // - Add the category for the test result, if necessary.
- for (Iterator entryIt = testResults.entrySet().iterator();
- entryIt.hasNext();) {
- Map.Entry entry = (Map.Entry) entryIt.next();
- TestCase test = (TestCase) entry.getKey();
- List/*<JUnitMessageQueue.TestResult>*/ results
- = (List/*<JUnitMessageQueue.TestResult>*/) entry.getValue();
- BenchmarkXml xml = new BenchmarkXml(test, results);
- Element categoryElement = getCategoryElement(doc, report,
- xml.metaData.getCategory().getClassName());
- categoryElement.appendChild(xml.toElement(doc));
- }
-
- return report;
- }
}
private static final String GWT_BENCHMARK_CATEGORY = "gwt.benchmark.category";
- private static final String GWT_BENCHMARK_DESCRIPTION
- = "gwt.benchmark.description";
+ private static final String GWT_BENCHMARK_DESCRIPTION = "gwt.benchmark.description";
private static final String GWT_BENCHMARK_NAME = "gwt.benchmark.name";
private static File findSourceFile(JClassType klass) {
final char separator = File.separator.charAt(0);
- String filePath = klass.getPackage().getName().replace('.', separator) +
- separator + klass.getSimpleSourceName() + ".java";
+ String filePath = klass.getPackage().getName().replace('.', separator)
+ + separator + klass.getSimpleSourceName() + ".java";
String[] paths = getClassPath();
for (int i = 0; i < paths.length; ++i) {
@@ -356,8 +338,7 @@
return path.split(File.pathSeparator);
}
- private static String getSimpleMetaData(HasMetaData hasMetaData,
- String name) {
+ private static String getSimpleMetaData(HasMetaData hasMetaData, String name) {
String[][] allValues = hasMetaData.getMetaData(name);
if (allValues == null) {
@@ -394,26 +375,23 @@
source.append("\n");
}
- char[] buf = new char[ source.length() ];
+ char[] buf = new char[source.length()];
source.getChars(0, buf.length, buf, 0);
return buf;
}
- private Map/*<String,Map<CategoryImpl>*/ testCategories
- = new HashMap/*<String,CategoryImpl>*/();
+ private Map/* <String,Map<CategoryImpl> */testCategories = new HashMap/* <String,CategoryImpl> */();
- private Map/*<String,Map<String,MetaData>>*/ testMetaData
- = new HashMap/*<String,Map<String,MetaData>>*/();
+ private Map/* <String,Map<String,MetaData>> */testMetaData = new HashMap/* <String,Map<String,MetaData>> */();
- private Map/*<TestCase,List<JUnitMessageQueue.TestResult>>*/ testResults
- = new HashMap/*<TestCase,JUnitMessageQueue.List<TestResult>>*/();
+ private Map/* <TestCase,List<JUnitMessageQueue.TestResult>> */testResults = new HashMap/* <TestCase,JUnitMessageQueue.List<TestResult>> */();
private TypeOracle typeOracle;
private TreeLogger logger;
- public BenchmarkReport( TreeLogger logger ) {
+ public BenchmarkReport(TreeLogger logger) {
this.logger = logger;
}
@@ -427,20 +405,17 @@
String categoryType = getSimpleMetaData(benchmarkClass,
GWT_BENCHMARK_CATEGORY);
- Map zeroArgMethods = BenchmarkGenerator
- .getNotOverloadedTestMethods(benchmarkClass);
- Map/*<String,JMethod>*/ parameterizedMethods = BenchmarkGenerator
- .getParameterizedTestMethods(benchmarkClass, TreeLogger.NULL);
- List/*<JMethod>*/ testMethods = new ArrayList/*<JMethod>*/(
+ Map zeroArgMethods = BenchmarkGenerator.getNotOverloadedTestMethods(benchmarkClass);
+ Map/* <String,JMethod> */parameterizedMethods = BenchmarkGenerator.getParameterizedTestMethods(
+ benchmarkClass, TreeLogger.NULL);
+ List/* <JMethod> */testMethods = new ArrayList/* <JMethod> */(
zeroArgMethods.size() + parameterizedMethods.size());
testMethods.addAll(zeroArgMethods.values());
testMethods.addAll(parameterizedMethods.values());
- Map/*<String,MetaData>*/ metaDataMap
- = (Map/*<String,MetaData>*/) testMetaData
- .get(benchmarkClass.toString());
+ Map/* <String,MetaData> */metaDataMap = (Map/* <String,MetaData> */) testMetaData.get(benchmarkClass.toString());
if (metaDataMap == null) {
- metaDataMap = new HashMap/*<String,MetaData>*/();
+ metaDataMap = new HashMap/* <String,MetaData> */();
testMetaData.put(benchmarkClass.toString(), metaDataMap);
}
@@ -451,8 +426,8 @@
} catch (IOException e) {
// if we fail to create the parser for some reason, we'll have to just
// deal with a null parser.
- logger.log(TreeLogger.WARN,
- "Unable to parse the code for " + benchmarkClass, e);
+ logger.log(TreeLogger.WARN, "Unable to parse the code for "
+ + benchmarkClass, e);
}
// Add all of the benchmark methods
@@ -465,22 +440,23 @@
methodCategoryType = categoryType;
}
CategoryImpl methodCategory = getCategory(methodCategoryType);
- String sourceCode = parser == null ? null : parser.getMethod(method);
+ StringBuffer sourceCode = parser == null ? null : new StringBuffer(
+ parser.getMethod(method));
StringBuffer summary = new StringBuffer();
StringBuffer comment = new StringBuffer();
getComment(sourceCode, summary, comment);
MetaData metaData = new MetaData(benchmarkClass.toString(), methodName,
- sourceCode, methodCategory, methodName, summary.toString());
+ sourceCode != null ? sourceCode.toString() : null, methodCategory,
+ methodName, summary.toString());
metaDataMap.put(methodName, metaData);
}
}
public void addBenchmarkResults(TestCase test, TestResults results) {
- List/*<TestResults>*/ currentResults = (List/*<TestResults>*/) testResults
- .get(test);
+ List/* <TestResults> */currentResults = (List/* <TestResults> */) testResults.get(test);
if (currentResults == null) {
- currentResults = new ArrayList/*<TestResults>*/();
+ currentResults = new ArrayList/* <TestResults> */();
testResults.put(test, currentResults);
}
currentResults.add(results);
@@ -489,12 +465,15 @@
/**
* Generates reports for all of the benchmarks which were added to the
* generator.
- *
+ *
* @param outputPath The path to write the reports to.
+ * @throws ParserConfigurationException
+ * @throws UnableToCompleteException
+ * @throws IOException
* @throws IOException If anything goes wrong writing to outputPath
*/
- public void generate(String outputPath)
- throws IOException, TransformerException, ParserConfigurationException {
+ public void generate(String outputPath) throws ParserConfigurationException,
+ UnableToCompleteException, IOException {
// Don't generate a new report if no tests were actually run.
if (testResults.size() == 0) {
@@ -503,25 +482,34 @@
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
-
Document doc = builder.newDocument();
doc.appendChild(new ReportXml().toElement(doc));
+ byte[] xmlBytes = Util.toXmlUtf8(doc);
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(outputPath);
+ fos.write(xmlBytes);
+ } finally {
+ Utility.close(fos);
+ }
- // TODO(tobyr) Looks like indenting is busted
- // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446
- // Not a big deal, since we don't intend to read the XML by hand anyway
- TransformerFactory transformerFactory = TransformerFactory.newInstance();
- // Think this can be used with JDK 1.5
- // transformerFactory.setAttribute( "indent-number", new Integer(2) );
- Transformer serializer = transformerFactory.newTransformer();
- serializer.setOutputProperty(OutputKeys.METHOD, "xml");
- serializer.setOutputProperty(OutputKeys.INDENT, "yes");
- serializer
- .setOutputProperty("{ http://xml.apache.org/xslt }indent-amount", "2");
- BufferedOutputStream docOut = new BufferedOutputStream(
- new FileOutputStream(outputPath));
- serializer.transform(new DOMSource(doc), new StreamResult(docOut));
- docOut.close();
+ // TODO(bruce) The code below is commented out because of GWT Issue 958.
+
+ // // TODO(tobyr) Looks like indenting is busted
+ // // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446
+ // // Not a big deal, since we don't intend to read the XML by hand anyway
+ // TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ // // Think this can be used with JDK 1.5
+ // // transformerFactory.setAttribute( "indent-number", new Integer(2) );
+ // Transformer serializer = transformerFactory.newTransformer();
+ // serializer.setOutputProperty(OutputKeys.METHOD, "xml");
+ // serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+ // serializer
+ // .setOutputProperty("{ http://xml.apache.org/xslt }indent-amount", "2");
+ // BufferedOutputStream docOut = new BufferedOutputStream(
+ // new FileOutputStream(outputPath));
+ // serializer.transform(new DOMSource(doc), new StreamResult(docOut));
+ // docOut.close();
}
private CategoryImpl getCategory(String name) {
@@ -554,7 +542,7 @@
* first sentence summary in <code>summary</code> and the body of the entire
* comment (including the summary) in <code>comment</code>.
*/
- private void getComment(String sourceCode, StringBuffer summary,
+ private void getComment(StringBuffer sourceCode, StringBuffer summary,
StringBuffer comment) {
if (sourceCode == null) {
@@ -569,20 +557,20 @@
Pattern p = Pattern.compile(regex, Pattern.DOTALL);
Matcher m = p.matcher(sourceCode);
- if (! m.find()) {
+ // Early out if there is no javadoc comment.
+ if (!m.find()) {
return;
}
String commentStr = m.group();
- p = Pattern.compile(
- "(/\\*\\*\\s*)" + // The comment header
+ p = Pattern.compile("(/\\*\\*\\s*)" + // The comment header
"(((\\s*\\**\\s*)([^\n\r]*)[\n\r]+)*)" // The comment body
);
m = p.matcher(commentStr);
- if (! m.find()) {
+ if (!m.find()) {
return;
}
@@ -601,5 +589,25 @@
}
comment.append(bareComment);
+
+ // Measure the indentation width on the second line to infer what the
+ // first line indent should be.
+ p = Pattern.compile("[^\\r\\n]+[\\r\\n]+(\\s+)\\*", Pattern.MULTILINE);
+ m = p.matcher(sourceCode);
+ int indentLen = 0;
+ if (m.find()) {
+ String indent = m.group(1);
+ indentLen = indent.length() - 1;
+ }
+ StringBuffer leadingIndent = new StringBuffer();
+ for (int i = 0; i < indentLen; ++i) {
+ leadingIndent.append(' ');
+ }
+
+ // By inserting at 0 here, we are assuming that sourceCode begins with
+ // /**, which is actually a function of how JDT sees a declaration start.
+ // If in the future, you see bogus indentation here, it means that this
+ // assumption is bad.
+ sourceCode.insert(0, leadingIndent);
}
-}
\ No newline at end of file
+}