Speed up SOYC compilation. Review by: spoon@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5482 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java index 8414263..f98fe68 100644 --- a/dev/core/src/com/google/gwt/core/ext/soyc/Range.java +++ b/dev/core/src/com/google/gwt/core/ext/soyc/Range.java
@@ -54,8 +54,8 @@ } }; - private final int end; - private final int start; + final int end; + final int start; /** * Constructor.
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/Story.java b/dev/core/src/com/google/gwt/core/ext/soyc/Story.java index e0f3ad4..f1eac77 100644 --- a/dev/core/src/com/google/gwt/core/ext/soyc/Story.java +++ b/dev/core/src/com/google/gwt/core/ext/soyc/Story.java
@@ -31,6 +31,7 @@ package com.google.gwt.core.ext.soyc; import java.io.Serializable; +import java.util.Set; import java.util.SortedSet; /** @@ -82,6 +83,5 @@ * string literals) that appear in multiple locations in the source may be * merged by the compiler into a single Story. */ - SortedSet<Origin> getSourceOrigin(); - -} \ No newline at end of file + Set<Origin> getSourceOrigin(); +}
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java index f5335ca..9bdc649 100644 --- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java +++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/DependencyRecorder.java
@@ -19,12 +19,10 @@ import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer; -import com.google.gwt.dev.util.HtmlTextOutput; import com.google.gwt.util.tools.Utility; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.zip.GZIPOutputStream; @@ -42,10 +40,7 @@ new DependencyRecorder().recordDependenciesImpl(logger, out, jprogram); } - private HtmlTextOutput htmlOut; - private PrintWriter pw; - - private OutputStreamWriter writer; + private StringBuilder builder; private DependencyRecorder() { } @@ -74,22 +69,29 @@ dependencyAnalyzer.setDependencyRecorder(this); try { - writer = new OutputStreamWriter(new GZIPOutputStream(out), "UTF-8"); - pw = new PrintWriter(writer); - htmlOut = new HtmlTextOutput(pw, false); + OutputStreamWriter writer + = new OutputStreamWriter(new GZIPOutputStream(out), "UTF-8"); + + StringBuilder localBuilder = new StringBuilder(); + this.builder = localBuilder; + + printPre(); + for (JMethod method : jprogram.getAllEntryMethods()) { + dependencyAnalyzer.traverseFrom(method); + if (localBuilder.length() > 8 * 1024) { + writer.write(localBuilder.toString()); + localBuilder.setLength(0); + } + } + printPost(); + + writer.write(localBuilder.toString()); + Utility.close(writer); + + logger.log(TreeLogger.INFO, "Done"); } catch (Throwable e) { - logger.log(TreeLogger.ERROR, "Could not open dependency file.", e); + logger.log(TreeLogger.ERROR, "Could not write dependency file.", e); } - - printPre(); - for (JMethod method : jprogram.getAllEntryMethods()) { - dependencyAnalyzer.traverseFrom(method); - } - printPost(); - pw.close(); - Utility.close(writer); - - logger.log(TreeLogger.INFO, "Done"); } /** @@ -99,55 +101,45 @@ * @param dependencyChain */ private void printMethodDependency(ArrayList<JMethod> dependencyChain) { - String curLine; - for (int i = dependencyChain.size() - 1; i >= 0; i--) { - JMethod curMethod = dependencyChain.get(i); - String sFullMethodString = curMethod.getName(); - if (curMethod.getEnclosingType() != null) { - sFullMethodString = curMethod.getEnclosingType().getName() + "::" - + curMethod.getName(); - } - if (i == dependencyChain.size() - 1) { - curLine = "<method name=\"" + sFullMethodString + "\">"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - htmlOut.indentIn(); - htmlOut.indentIn(); - } else { - curLine = "<called by=\"" + sFullMethodString + "\"/>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - } + int size = dependencyChain.size(); + if (size == 0) { + return; } - htmlOut.indentOut(); - htmlOut.indentOut(); - curLine = "</method>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); + + JMethod curMethod = dependencyChain.get(size - 1); + builder.append("<method name=\""); + if (curMethod.getEnclosingType() != null) { + builder.append(curMethod.getEnclosingType().getName()); + builder.append("::"); + } + builder.append(curMethod.getName()); + builder.append("\">\n"); + + for (int i = size - 2; i >= 0; i--) { + curMethod = dependencyChain.get(i); + builder.append("<called by=\""); + if (curMethod.getEnclosingType() != null) { + builder.append(curMethod.getEnclosingType().getName()); + builder.append("::"); + } + builder.append(curMethod.getName()); + builder.append("\"/>\n"); + } + builder.append("</method>\n"); } /** * Prints the closing lines necessary for the dependencies file. */ private void printPost() { - String curLine = "</soyc-dependencies>"; - htmlOut.indentOut(); - htmlOut.indentOut(); - htmlOut.printRaw(curLine); - htmlOut.newline(); + builder.append("</soyc-dependencies>\n"); } /** * Prints the preamble necessary for the dependencies file. */ private void printPre() { - String curLine = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - curLine = "<soyc-dependencies>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - htmlOut.indentIn(); - htmlOut.indentIn(); + builder.append( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soyc-dependencies>\n"); } }
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java index 6526b3e..b99bcbb 100644 --- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java +++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryImpl.java
@@ -19,8 +19,8 @@ import com.google.gwt.core.ext.soyc.Story; import java.io.Serializable; -import java.util.Collections; import java.util.Comparator; +import java.util.Set; import java.util.SortedSet; /** @@ -43,14 +43,14 @@ private final int length; private final String literalDescription; private final SortedSet<Member> members; - private final SortedSet<Origin> origins; + private final Set<Origin> origins; /** * Standard constructor. This constructor will create unmodifiable versions of * the collections passed into it. */ public StoryImpl(int id, SortedSet<Member> members, - SortedSet<Origin> origins, String literalDescription, int fragment, + Set<Origin> origins, String literalDescription, int fragment, int length) { assert members != null; assert origins != null; @@ -63,8 +63,8 @@ this.length = length; this.literalDescription = literalDescription == null ? null : literalDescription.intern(); - this.members = Collections.unmodifiableSortedSet(members); - this.origins = Collections.unmodifiableSortedSet(origins); + this.members = members; + this.origins = origins; } /** @@ -116,7 +116,7 @@ return members; } - public SortedSet<Origin> getSourceOrigin() { + public Set<Origin> getSourceOrigin() { return origins; }
diff --git a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryRecorder.java b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryRecorder.java index c2dfafa..32dfb5d 100644 --- a/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryRecorder.java +++ b/dev/core/src/com/google/gwt/core/ext/soyc/impl/StoryRecorder.java
@@ -29,12 +29,11 @@ import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.js.ast.JsFunction; -import com.google.gwt.dev.util.HtmlTextOutput; +import com.google.gwt.dev.util.Util; import com.google.gwt.util.tools.Utility; +import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -67,6 +66,8 @@ } } + private static final int MAX_STRING_BUILDER_SIZE = 65536; + /** * Used to record dependencies of a program. */ @@ -75,35 +76,35 @@ new StoryRecorder().recordStoriesImpl(logger, out, sourceInfoMaps, js); } + private StringBuilder builder; + private int curHighestFragment = 0; - private HtmlTextOutput htmlOut; + private OutputStream gzipStream; + private String[] js; /** * Used by {@link #popAndRecord(Stack)} to determine start and end ranges. */ private int lastEnd = 0; + /** * This is a class field for convenience, but it should be deleted at the end * of the constructor. */ private transient Map<Correlation, Member> membersByCorrelation = new IdentityHashMap<Correlation, Member>(); - private PrintWriter pw; - + /** * This is a class field for convenience, but it should be deleted at the end * of the constructor. */ private transient Map<SourceInfo, StoryImpl> storyCache = new IdentityHashMap<SourceInfo, StoryImpl>(); - private Map<Story, Integer> storyIds = new HashMap<Story, Integer>(); - private OutputStreamWriter writer; - private StoryRecorder() { } - + protected void recordStoriesImpl(TreeLogger logger, OutputStream out, List<Map<Range, SourceInfo>> sourceInfoMaps, String[] js) { @@ -112,25 +113,8 @@ this.js = js; try { - writer = new OutputStreamWriter(new GZIPOutputStream(out), "UTF-8"); - pw = new PrintWriter(writer); - htmlOut = new HtmlTextOutput(pw, false); - - String curLine = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - curLine = "<soyc>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - htmlOut.indentIn(); - htmlOut.indentIn(); - - curLine = "<stories>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - htmlOut.indentIn(); - htmlOut.indentIn(); + builder = new StringBuilder(MAX_STRING_BUILDER_SIZE * 2); + gzipStream = new GZIPOutputStream(out); /* * Don't retain beyond the constructor to avoid lingering references to @@ -145,13 +129,20 @@ Member.SOURCE_NAME_COMPARATOR); Set<SourceInfo> sourceInfoSeen = new HashSet<SourceInfo>(); + builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soyc>\n<stories>\n"); + int fragment = 0; for (Map<Range, SourceInfo> sourceInfoMap : sourceInfoMaps) { lastEnd = 0; analyzeFragment(memberFactory, classesMutable, functionsMutable, sourceInfoMap, sourceInfoSeen, fragment++); + + // Flush output to improve memory locality + flushOutput(); } + builder.append("</stories>\n</soyc>\n"); + /* * Clear the member fields that we don't need anymore to allow GC of the * SourceInfo objects @@ -159,33 +150,20 @@ membersByCorrelation = null; storyCache = null; - htmlOut.indentOut(); - htmlOut.indentOut(); - curLine = "</stories>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - htmlOut.indentOut(); - htmlOut.indentOut(); - curLine = "</soyc>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - Utility.close(writer); - pw.close(); + Util.writeUtf8(builder, gzipStream); + Utility.close(gzipStream); logger.log(TreeLogger.INFO, "Done"); - } catch (Throwable e) { - logger.log(TreeLogger.ERROR, "Could not open dependency file.", e); + logger.log(TreeLogger.ERROR, "Could not write dependency file.", e); } } - + private void analyzeFragment(MemberFactory memberFactory, TreeSet<ClassMember> classesMutable, TreeSet<FunctionMember> functionsMutable, Map<Range, SourceInfo> sourceInfoMap, Set<SourceInfo> sourceInfoSeen, - int fragment) { + int fragment) throws IOException { /* * We want to iterate over the Ranges so that enclosing Ranges come before * their enclosed Ranges... @@ -215,7 +193,10 @@ // Possibly create and record Members if (!sourceInfoSeen.contains(info)) { sourceInfoSeen.add(info); - for (Correlation c : info.getPrimaryCorrelations()) { + for (Correlation c : info.getPrimaryCorrelationsArray()) { + if (c == null) { + continue; + } if (membersByCorrelation.containsKey(c)) { continue; } @@ -255,17 +236,6 @@ } } - /* - * Record dependencies as observed in the structure of the JS output. This - * an an ad-hoc approach that just looks at which SourceInfos are used - * within the Range of another SourceInfo. - */ - Set<Correlation> correlationsInScope = new HashSet<Correlation>(); - for (RangeInfo outer : dependencyScope) { - SourceInfo outerInfo = outer.info; - correlationsInScope.addAll(outerInfo.getPrimaryCorrelations()); - } - dependencyScope.push(new RangeInfo(range, info)); } @@ -283,8 +253,7 @@ assert dependencyOrder[0].getEnd() == lastEnd; } - private void emitStory(StoryImpl story, Range range) { - + private void emitStory(StoryImpl story, Range range) throws IOException { int storyNum; if (storyIds.containsKey(story)) { storyNum = storyIds.get(story); @@ -293,109 +262,65 @@ storyIds.put(story, storyNum); } - String curLine = "<story id=\"story" + Integer.toString(storyNum) + "\""; + builder.append("<story id=\"story"); + builder.append(storyNum); if (story.getLiteralTypeName() != null) { - curLine = curLine + " literal=\"" + story.getLiteralTypeName() + "\""; + builder.append("\" literal=\""); + builder.append(story.getLiteralTypeName()); } - curLine = curLine + ">"; - htmlOut.printRaw(curLine); - htmlOut.newline(); + builder.append("\">\n"); Set<Origin> origins = story.getSourceOrigin(); if (origins.size() > 0) { - htmlOut.indentIn(); - htmlOut.indentIn(); - - curLine = "<origins>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - htmlOut.indentIn(); - htmlOut.indentIn(); - } - for (Origin origin : origins) { - curLine = "<origin lineNumber=\"" - + Integer.toString(origin.getLineNumber()) + "\" location=\"" - + origin.getLocation() + "\"/>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - } - if (origins.size() > 0) { - htmlOut.indentOut(); - htmlOut.indentOut(); - - curLine = "</origins>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - htmlOut.indentOut(); - htmlOut.indentOut(); + builder.append("<origins>\n"); + for (Origin origin : origins) { + builder.append("<origin lineNumber=\""); + builder.append(origin.getLineNumber()); + builder.append("\" location=\""); + builder.append(origin.getLocation()); + builder.append("\"/>\n"); + + flushOutput(); + } + builder.append("</origins>\n"); } Set<Member> correlations = story.getMembers(); if (correlations.size() > 0) { - htmlOut.indentIn(); - htmlOut.indentIn(); - - curLine = "<correlations>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - htmlOut.indentIn(); - htmlOut.indentIn(); - } - for (Member correlation : correlations) { - curLine = "<by idref=\"" + correlation.getSourceName() + "\"/>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - } - if (correlations.size() > 0) { - htmlOut.indentOut(); - htmlOut.indentOut(); - - curLine = "</correlations>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - htmlOut.indentOut(); - htmlOut.indentOut(); + builder.append("<correlations>\n"); + for (Member correlation : correlations) { + builder.append("<by idref=\""); + builder.append(correlation.getSourceName()); + builder.append("\"/>\n"); + + flushOutput(); + } + builder.append("</correlations>\n"); } - htmlOut.indentIn(); - htmlOut.indentIn(); + builder.append("<js fragment=\""); + builder.append(curHighestFragment); + builder.append("\"/>\n<storyref idref=\"story"); + builder.append(storyNum); - curLine = "<js fragment=\"" + curHighestFragment + "\"/>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); - - String jsCode = js[curHighestFragment].substring(range.getStart(), - range.getEnd()); - jsCode = escapeXml(jsCode); - if ((jsCode.length() == 0) || (jsCode.compareTo("\n") == 0)) { - curLine = "<storyref idref=\"story" + Integer.toString(storyNum) + "\"/>"; + int start = range.getStart(); + int end = range.getEnd(); + String jsCode = js[curHighestFragment]; + if ((start == end) || ((end == start + 1) && jsCode.charAt(start) == '\n')) { + builder.append("\"/>\n</story>\n"); } else { - curLine = "<storyref idref=\"story" + Integer.toString(storyNum) + "\">" - + jsCode + "</storyref>"; + builder.append("\">"); + Util.escapeXml(jsCode, start, end, false, builder); + builder.append("</storyref>\n</story>\n"); } - - htmlOut.printRaw(curLine); - htmlOut.newline(); - - htmlOut.indentOut(); - htmlOut.indentOut(); - - curLine = "</story>"; - htmlOut.printRaw(curLine); - htmlOut.newline(); } - private String escapeXml(String unescaped) { - String escaped = unescaped.replaceAll("\\&", "&"); - escaped = escaped.replaceAll("\\<", "<"); - escaped = escaped.replaceAll("\\>", ">"); - escaped = escaped.replaceAll("\\\"", """); - // escaped = escaped.replaceAll("\\'", "'"); - return escaped; + private void flushOutput() throws IOException { + // Flush output to improve memory locality + if (builder.length() > MAX_STRING_BUILDER_SIZE) { + Util.writeUtf8(builder, gzipStream); + builder.setLength(0); + } } /** @@ -403,7 +328,8 @@ * the right length, possibly sub-dividing the super-enclosing Range in the * process. */ - private void popAndRecord(Stack<RangeInfo> dependencyScope, int fragment) { + private void popAndRecord(Stack<RangeInfo> dependencyScope, int fragment) + throws IOException { RangeInfo rangeInfo = dependencyScope.pop(); Range toStore = rangeInfo.range; @@ -416,7 +342,7 @@ assert !dependencyScope.isEmpty(); SourceInfo gapInfo = dependencyScope.peek().info; - recordStory(gapInfo, fragment, newRange.length(), newRange); + recordStory(gapInfo, fragment, newRange.length(), newRange, builder); lastEnd += newRange.length(); } @@ -429,13 +355,13 @@ if (lastEnd < toStore.getEnd()) { Range newRange = new Range(Math.max(lastEnd, toStore.getStart()), toStore.getEnd()); - recordStory(rangeInfo.info, fragment, newRange.length(), newRange); + recordStory(rangeInfo.info, fragment, newRange.length(), newRange, builder); lastEnd += newRange.length(); } } private void recordStory(SourceInfo info, int fragment, int length, - Range range) { + Range range, StringBuilder builder) throws IOException { assert info != null; assert storyCache != null; @@ -445,20 +371,19 @@ StoryImpl theStory; if (!storyCache.containsKey(info)) { - SortedSet<Member> members = new TreeSet<Member>( Member.TYPE_AND_SOURCE_NAME_COMPARATOR); + Set<Origin> origins = new HashSet<Origin>(); for (Correlation c : info.getAllCorrelations()) { Member m = membersByCorrelation.get(c); if (m != null) { members.add(m); } - } - - SortedSet<Origin> origins = new TreeSet<Origin>(); - for (Correlation c : info.getAllCorrelations(Axis.ORIGIN)) { - origins.add(new OriginImpl(c.getOrigin())); + + if (c.getAxis() == Axis.ORIGIN) { + origins.add(new OriginImpl(c.getOrigin())); + } } String literalType = null; @@ -477,4 +402,5 @@ emitStory(theStory, range); } + }
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java index e3bd98d..13929d3 100644 --- a/dev/core/src/com/google/gwt/dev/CompilePerms.java +++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -196,7 +196,7 @@ List<FileBackedObject<PermutationResult>> resultFiles) throws UnableToCompleteException { final TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling " - + perms.length + " permutations"); + + perms.length + " permutation" + (perms.length > 1 ? "s" : "")); PermutationWorkerFactory.compilePermutations(logger, precompilation, perms, localWorkers, resultFiles); branch.log(TreeLogger.INFO, "Permutation compile succeeded");
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index 7b0de33..f440994 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -183,6 +183,7 @@ public static PermutationResult compilePermutation(TreeLogger logger, UnifiedAst unifiedAst, Map<String, String> rebindAnswers, PropertyOracle[] propertyOracles, int permutationId) throws UnableToCompleteException { + long permStart = System.currentTimeMillis(); try { if (JProgram.isTracingEnabled()) { System.out.println("------------------------------------------------------------"); @@ -323,10 +324,13 @@ PermutationResult toReturn = new PermutationResultImpl(js, makeSymbolMap(symbolTable)); - if (sourceInfoMaps != null) { + if (sourceInfoMaps != null) { + long soycStart = System.currentTimeMillis(); + System.out.println("Computing SOYC output"); // Free up memory. symbolTable = null; + long start = System.currentTimeMillis(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // get method dependencies StoryRecorder.recordStories(logger, baos, sourceInfoMaps, js); @@ -334,20 +338,34 @@ + ".xml.gz", baos.toByteArray()); // Free up memory. js = null; + System.out.println("Record stories: " + + (System.currentTimeMillis() - start) + " ms"); + start = System.currentTimeMillis(); baos.reset(); DependencyRecorder.recordDependencies(logger, baos, jprogram); SoycArtifact dependencies = new SoycArtifact("dependencies" + permutationId + ".xml.gz", baos.toByteArray()); + System.out.println("Record dependencies: " + + (System.currentTimeMillis() - start) + " ms"); + start = System.currentTimeMillis(); baos.reset(); SplitPointRecorder.recordSplitPoints(jprogram, baos, logger); SoycArtifact splitPoints = new SoycArtifact("splitPoints" + permutationId + ".xml.gz", baos.toByteArray()); + System.out.println("Record split points: " + + (System.currentTimeMillis() - start) + " ms"); toReturn.getArtifacts().add( new StandardCompilationAnalysis(dependencies, stories, splitPoints)); + + System.out.println("Completed SOYC phase in " + + (System.currentTimeMillis() - soycStart) + " ms"); } + + System.out.println("Permutation took " + + (System.currentTimeMillis() - permStart) + " ms"); return toReturn; } catch (Throwable e) { throw logAndTranslateException(logger, e);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java index baaf810..a34a2b0 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java +++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
@@ -64,6 +64,13 @@ * has been set. */ Set<Correlation> getPrimaryCorrelations(); + + /** + * Returns the first Correlations added along each Axis on which a Correlation + * has been set. Some entries may be null and should be ignored. The + * returned array must not be modified. + */ + Correlation[] getPrimaryCorrelationsArray(); int getStartLine();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java b/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java index d7221ca..cb96475 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java +++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfoCorrelation.java
@@ -36,9 +36,11 @@ * Micro-opt for {@link #makeChild(Class, String)}. */ private static final SourceInfo[] EMPTY_SOURCEINFO_ARRAY = new SourceInfo[0]; + + private static final int numCorrelationAxes = Axis.values().length; private static int numCorrelationAxes() { - return Axis.values().length; + return numCorrelationAxes; } /** @@ -165,6 +167,10 @@ } return toReturn; } + + public Correlation[] getPrimaryCorrelationsArray() { + return primaryCorrelations; + } public int getStartLine() { return getOrigin().getStartLine();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java index 8224780..f06dba8 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java +++ b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
@@ -166,6 +166,10 @@ public Set<Correlation> getPrimaryCorrelations() { return Collections.emptySet(); } + + public Correlation[] getPrimaryCorrelationsArray() { + return new Correlation[0]; + } public int getStartLine() { return startLine;
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 70cf7e3..f06b447 100644 --- a/dev/core/src/com/google/gwt/dev/util/Util.java +++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -307,12 +307,65 @@ * Escapes '&', '<', '>', '"', and '\'' to their XML entity equivalents. */ public static String escapeXml(String unescaped) { - String escaped = unescaped.replaceAll("\\&", "&"); - escaped = escaped.replaceAll("\\<", "<"); - escaped = escaped.replaceAll("\\>", ">"); - escaped = escaped.replaceAll("\\\"", """); - escaped = escaped.replaceAll("\\'", "'"); - return escaped; + StringBuilder builder = new StringBuilder(); + escapeXml(unescaped, 0, unescaped.length(), true, builder); + return builder.toString(); + } + + /** + * Escapes '&', '<', '>', '"', and optionally ''' to their XML entity + * equivalents. The portion of the input string between start (inclusive) and + * end (exclusive) is scanned. The output is appended to the given + * StringBuilder. + * + * @param code the input String + * @param start the first character position to scan. + * @param end the character position following the last character to scan. + * @param quoteApostrophe if true, the ' character is quoted as + * &apos; + * @param builder a StringBuilder to be appended with the output. + */ + public static void escapeXml(String code, int start, int end, + boolean quoteApostrophe, StringBuilder builder) { + int lastIndex = 0; + int len = end - start; + char[] c = new char[len]; + + code.getChars(start, end, c, 0); + for (int i = 0; i < len; i++) { + switch (c[i]) { + case '&': + builder.append(c, lastIndex, i - lastIndex); + builder.append("&"); + lastIndex = i + 1; + break; + case '>': + builder.append(c, lastIndex, i - lastIndex); + builder.append(">"); + lastIndex = i + 1; + break; + case '<': + builder.append(c, lastIndex, i - lastIndex); + builder.append("<"); + lastIndex = i + 1; + break; + case '\"': + builder.append(c, lastIndex, i - lastIndex); + builder.append("""); + lastIndex = i + 1; + break; + case '\'': + if (quoteApostrophe) { + builder.append(c, lastIndex, i - lastIndex); + builder.append("'"); + lastIndex = i + 1; + } + break; + default: + break; + } + } + builder.append(c, lastIndex, len - lastIndex); } public static URL findSourceInClassPath(ClassLoader cl, String sourceTypeName) { @@ -1211,6 +1264,72 @@ Utility.close(stream); } } + + /** + * Writes the contents of a StringBuilder to an OutputStream, encoding + * each character using the UTF-* encoding. Unicode characters between + * U+0000 and U+10FFFF are supported. + */ + public static void writeUtf8(StringBuilder builder, OutputStream out) + throws IOException { + // Rolling our own converter avoids the following: + // + // o Instantiating the entire builder as a String + // o Creating CharEncoders and NIO buffer + // o Passing through an OutputStreamWriter + + int buflen = 1024; + char[] inBuf = new char[buflen]; + byte[] outBuf = new byte[4 * buflen]; + + int length = builder.length(); + int start = 0; + + while (start < length) { + int end = Math.min(start + buflen, length); + builder.getChars(start, end, inBuf, 0); + + int index = 0; + int len = end - start; + for (int i = 0; i < len; i++) { + int c = inBuf[i] & 0xffff; + if (c < 0x80) { + outBuf[index++] = (byte) c; + } else if (c < 0x800) { + int y = c >> 8; + int x = c & 0xff; + outBuf[index++] = (byte) (0xc0 | (y << 2) | (x >> 6)); // 110yyyxx + outBuf[index++] = (byte) (0x80 | (x & 0x3f)); // 10xxxxxx + } else if (c < 0xD800 || c > 0xDFFF) { + int y = (c >> 8) & 0xff; + int x = c & 0xff; + outBuf[index++] = (byte) (0xe0 | (y >> 4)); // 1110yyyy + outBuf[index++] = (byte) (0x80 | ((y << 2) & 0x3c) | (x >> 6)); // 10yyyyxx + outBuf[index++] = (byte) (0x80 | (x & 0x3f)); // 10xxxxxx + } else { + // Ignore if no second character (which is not be legal unicode) + if (i + 1 < len) { + int hi = c & 0x3ff; + int lo = inBuf[i + 1] & 0x3ff; + + int full = 0x10000 + ((hi << 10) | lo); + int z = (full >> 16) & 0xff; + int y = (full >> 8) & 0xff; + int x = full & 0xff; + + outBuf[index++] = (byte) (0xf0 | (z >> 5)); + outBuf[index++] = (byte) (0x80 | ((z << 4) & 0x30) | (y >> 4)); + outBuf[index++] = (byte) (0x80 | ((y << 2) & 0x3c) | (x >> 6)); + outBuf[index++] = (byte) (0x80 | (x & 0x3f)); + + i++; // char has been consumed + } + } + } + out.write(outBuf, 0, index); + start = end; + } + } // /** // * Write all of the supplied bytes to the file, in a way that they can be
diff --git a/dev/core/test/com/google/gwt/core/ext/util/UtilSuite.java b/dev/core/test/com/google/gwt/core/ext/util/UtilSuite.java new file mode 100644 index 0000000..291dc81 --- /dev/null +++ b/dev/core/test/com/google/gwt/core/ext/util/UtilSuite.java
@@ -0,0 +1,27 @@ +/* + * Copyright 2009 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.core.ext.util; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class UtilSuite extends TestSuite { + public static Test suite() { + TestSuite suite = new TestSuite("Test suite for Util"); + suite.addTestSuite(UtilTest.class); + return suite; + } +}
diff --git a/dev/core/test/com/google/gwt/core/ext/util/UtilTest.java b/dev/core/test/com/google/gwt/core/ext/util/UtilTest.java new file mode 100644 index 0000000..5b3c394 --- /dev/null +++ b/dev/core/test/com/google/gwt/core/ext/util/UtilTest.java
@@ -0,0 +1,107 @@ +/* + * 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.core.ext.util; + +import com.google.gwt.dev.util.Util; + +import junit.framework.TestCase; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; + +/** + * Tests for some com.google.gwt.dev.util.Util methods. + */ +public class UtilTest extends TestCase { + + public UtilTest() { + } + + public void testExpandXml() { + String[] INPUTS = { + "ab&yoohoo<ok", + "ab&yoohoo<", + "&yoohoo<ok", + "ab&<ok", + "ab&yoohoo<ok", + "&", + "\'", + "\"", + ">", + "<" + }; + + String[] OUTPUTS = { + "ab&yoohoo<ok", + "ab&yoohoo<", + "&yoohoo<ok", + "ab&<ok", + "ab&yoohoo<ok", + "&", + "'", + """, + ">", + "<" + }; + + for (int i = 0; i < INPUTS.length; i++) { + assertEquals(Util.escapeXml(INPUTS[i]), OUTPUTS[i]); + } + } + + private void testWriteUtf8(String input) throws IOException { + // Built-in encoder + ByteArrayOutputStream out1 = new ByteArrayOutputStream(); + Writer writer = new OutputStreamWriter(out1, "UTF-8"); + writer.write(input); + writer.close(); + byte[] bytes1 = out1.toByteArray(); + + // Util encoder + StringBuilder builder = new StringBuilder(); + builder.append(input); + ByteArrayOutputStream out2 = new ByteArrayOutputStream(); + Util.writeUtf8(builder, out2); + byte[] bytes2 = out2.toByteArray(); + + assertEquals(bytes1.length, bytes2.length); + for (int i = 0; i < bytes1.length; i++) { + assertEquals("input = " + input + " at byte " + i, bytes1[i], bytes2[i]); + } + } + + public void testWriteUtf8() { + String[] INPUTS = { + "灰色", + "plain ascii text", + "té×ţ ŵıŦƕ lŎts œf wƎiƦd ƇhÅrȿ", + "ĝréý", + "$ᄋme סf everりthınğ งฆฅฦศ ႳႹႩႨႵ ᄃᄥᄷᄨᎤᎸᎹᎺᴓ", + "ᴄᴅᴈᴇἓἤἥἨὙΩ№℗℘←↑→↓✤✥✦✧�ﺕﺖꀕꀖꀗ逅逖逡遇違遝遭遷", + "Surrogate pairs: 𝍠𐀁𐀵𐅇𐅋𐅊𝄐𝄑𝄢𝄫𝄞𝍤𝍦𝍨再善" + }; + + try { + for (String input : INPUTS) { + testWriteUtf8(input); + } + } catch (IOException e) { + fail("Got IOException"); + } + } +}