SOYC work to correlate SourceInfo objects with Classes, Methods, Fields, and Functions.
Refactor DefaultTextOutput into a base class and add an HTML/XML-safe TextOutput.
Patch by: bobv
Review by: kprobst, spoon
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3760 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/Correlation.java b/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
new file mode 100644
index 0000000..f077d86
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/Correlation.java
@@ -0,0 +1,244 @@
+/*
+ * 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.dev.jjs;
+
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.jjs.ast.JType;
+import com.google.gwt.dev.js.ast.JsFunction;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Each SourceInfo may define one or more axes by which it can be correlated
+ * with other SourceInfo objects. Correlation has set and map-key semantics.
+ */
+public final class Correlation implements Serializable {
+ /*
+ * NB: The Correlation type uses AST nodes in its factory methods to make it
+ * easier to extract whatever information we want to include in the SOYC
+ * reports without having to update call sites with additional parameters.
+ *
+ * In the general case, references to AST nodes should not be exposed to any
+ * public-API consumers of the Correlation.
+ */
+
+ /**
+ * The axes on which we'll want to pivot the SourceInfo data-set.
+ */
+ public enum Axis {
+ /*
+ * TODO(bobv): Consider whether or not this should be a proper class
+ * hierarchy. The nice thing about an enum is that all possible types are
+ * programmatically enumerable.
+ *
+ * Also, consider MODULE and PACKAGE values.
+ */
+
+ /**
+ * Represents a physical source file.
+ */
+ FILE(true, true),
+
+ /**
+ * A Java class or interface type.
+ */
+ CLASS(true, false),
+
+ /**
+ * A Java method.
+ */
+ METHOD(true, false),
+
+ /**
+ * A field defined within a Java type.
+ */
+ FIELD(true, false),
+
+ /**
+ * A JavaScript function derived from a class or method.
+ */
+ FUNCTION(false, true);
+
+ private final boolean isJava;
+ private final boolean isJs;
+
+ /**
+ * Arguments indicate which AST the axis is relevant to.
+ */
+ private Axis(boolean isJava, boolean isJs) {
+ this.isJava = isJava;
+ this.isJs = isJs;
+ }
+
+ public boolean isJava() {
+ return isJava;
+ }
+
+ public boolean isJs() {
+ return isJs;
+ }
+ }
+
+ /**
+ * Compares Correlations based on axis and idents. Note that due to inherent
+ * limitations of mapping AST nodes into Strings, this Comparator may not
+ * always agree with {@link Correlation#equals(Object)}.
+ */
+ public static final Comparator<Correlation> AXIS_IDENT_COMPARATOR = new Comparator<Correlation>() {
+ public int compare(Correlation a, Correlation b) {
+ int r = a.axis.compareTo(b.axis);
+ if (r != 0) {
+ return r;
+ }
+
+ return a.ident.compareTo(b.ident);
+ }
+ };
+
+ public static Correlation by(JField field) {
+ return new Correlation(Axis.FIELD, field.getEnclosingType().getName()
+ + "::" + field.getName(), field);
+ }
+
+ public static Correlation by(JMethod method) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(method.getEnclosingType().getName()).append("::");
+ sb.append(method.getName()).append("(");
+ for (JType type : method.getOriginalParamTypes()) {
+ sb.append(type.getJsniSignatureName());
+ }
+ sb.append(")");
+
+ return new Correlation(Axis.METHOD, sb.toString(), method);
+ }
+
+ public static Correlation by(JReferenceType type) {
+ return new Correlation(Axis.CLASS, type.getName(), type);
+ }
+
+ public static Correlation by(JsFunction function) {
+ return new Correlation(Axis.FUNCTION, function.getName().getIdent(),
+ function);
+ }
+
+ /**
+ * Constructs a {@link Axis#FILE} Correlation.
+ */
+ public static Correlation by(String filename) {
+ return new Correlation(Axis.FILE, filename, filename);
+ }
+
+ /**
+ * This may contain a reference to either a Java or Js AST node.
+ */
+ protected final Object astReference;
+
+ protected final Axis axis;
+
+ /**
+ * This should be a uniquely-identifying value within the Correlation's axis
+ * that is suitable for human consumption. It may be the case that two
+ * Correlations have different AST references, but the same calculated ident,
+ * so this should not be relied upon for uniqueness.
+ */
+ protected final String ident;
+
+ private Correlation(Axis axis, String ident, Object astReference) {
+ if (ident == null) {
+ throw new NullPointerException("ident");
+ }
+
+ this.axis = axis;
+ this.ident = ident;
+ this.astReference = astReference;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Correlation)) {
+ return false;
+ }
+ Correlation c = (Correlation) obj;
+
+ boolean astSame = astReference == c.astReference
+ || (astReference != null && astReference.equals(c.astReference));
+ return axis.equals(c.axis) && astSame;
+ }
+
+ public Axis getAxis() {
+ return axis;
+ }
+
+ public JField getField() {
+ if (axis == Axis.FIELD) {
+ return (JField) astReference;
+ } else {
+ return null;
+ }
+ }
+
+ public JsFunction getFunction() {
+ if (axis == Axis.FUNCTION) {
+ return (JsFunction) astReference;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a human-readable identifier that can be used to identify the
+ * Correlation within its axis.
+ */
+ public String getIdent() {
+ return ident;
+ }
+
+ public JMethod getMethod() {
+ if (axis == Axis.METHOD) {
+ return (JMethod) astReference;
+ } else {
+ return null;
+ }
+ }
+
+ public JReferenceType getType() {
+ if (axis == Axis.CLASS) {
+ return (JReferenceType) astReference;
+ } else if (axis == Axis.METHOD) {
+ return ((JMethod) astReference).getEnclosingType();
+ } else if (axis == Axis.FIELD) {
+ return ((JField) astReference).getEnclosingType();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return 37 * axis.hashCode() + astReference.hashCode() + 13;
+ }
+
+ /**
+ * Defined for debugging convenience.
+ */
+ @Override
+ public String toString() {
+ return axis.toString() + ": " + ident;
+ }
+}
\ No newline at end of file
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 fdc5741..2c57e5c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -70,7 +70,6 @@
import com.google.gwt.dev.js.JsUnusedFunctionRemover;
import com.google.gwt.dev.js.JsVerboseNamer;
import com.google.gwt.dev.js.SourceInfoHistogram;
-import com.google.gwt.dev.js.SourceInfoHistogram.HistogramData;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.PerfLogger;
@@ -268,7 +267,6 @@
private final long astMemoryUsage;
private final String[] declEntryPoints;
- private final HistogramData histogramData;
private final Object myLockObject = new Object();
private final JJSOptions options;
private final Set<IProblem> problemSet = new HashSet<IProblem>();
@@ -343,12 +341,6 @@
// BuildTypeMap can uncover syntactic JSNI errors; report & abort
checkForErrors(logger, goldenCuds, true);
- if (enableDescendants) {
- histogramData = SourceInfoHistogram.exec(jprogram);
- } else {
- histogramData = null;
- }
-
// Compute all super type/sub type info
jprogram.typeOracle.computeBeforeAST();
@@ -426,7 +418,8 @@
}
} catch (IOException e) {
throw new RuntimeException(
- "Should be impossible to get an IOException reading an in-memory stream");
+ "Should be impossible to get an IOException reading an in-memory stream",
+ e);
} catch (Throwable e) {
throw logAndTranslateException(logger, e);
} finally {
@@ -586,9 +579,8 @@
JsIEBlockSizeVisitor.exec(jsProgram);
// Write the SOYC reports into the output
- if (histogramData != null) {
- SourceInfoHistogram.exec(jsProgram, histogramData,
- options.getSoycOutputDir());
+ if (options.getSoycOutputDir() != null) {
+ SourceInfoHistogram.exec(jsProgram, options.getSoycOutputDir());
}
// (12) Generate the final output text.
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 a95794e..cd5c570 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
@@ -15,14 +15,18 @@
*/
package com.google.gwt.dev.jjs;
+import com.google.gwt.dev.jjs.Correlation.Axis;
+
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumMap;
+import java.util.EnumSet;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -31,72 +35,116 @@
public class SourceInfo implements Serializable {
/**
+ * A totally-immutable version of SourceInfo.
+ */
+ protected static class Immutable extends SourceInfo {
+ public Immutable(int startPos, int endPos, int startLine, String fileName,
+ boolean createDescendants) {
+ super(startPos, endPos, startLine, fileName, createDescendants);
+ }
+
+ @Override
+ public void addAdditonalAncestors(SourceInfo... sourceInfos) {
+ throw new UnsupportedOperationException(
+ "May not add additional ancestors to the " + getFileName()
+ + " SourceInfo");
+ }
+
+ @Override
+ public void addCorrelation(Correlation c) {
+ throw new UnsupportedOperationException(
+ "May not add correlations to the " + getFileName() + "SourceInfo");
+ }
+
+ @Override
+ public void addSupertypeAncestors(SourceInfo... sourceInfos) {
+ throw new UnsupportedOperationException(
+ "May not add supertype ancestors to the " + getFileName()
+ + " SourceInfo");
+ }
+ }
+
+ /**
+ * Compares SourceInfos by their file and position information.
+ */
+ public static final Comparator<SourceInfo> LOCATION_COMPARATOR = new Comparator<SourceInfo>() {
+ public int compare(SourceInfo q, SourceInfo b) {
+ int a = q.getFileName().compareTo(b.getFileName());
+ if (a != 0) {
+ return a;
+ }
+
+ a = q.startPos - q.startPos;
+ if (a != 0) {
+ return a;
+ }
+
+ a = q.endPos - b.endPos;
+ if (a != 0) {
+ return a;
+ }
+
+ a = q.startLine - b.startLine;
+ if (a != 0) {
+ return a;
+ }
+
+ return 0;
+ }
+ };
+
+ /**
* Indicates that the source for an AST element is unknown. This indicates a
* deficiency in the compiler.
*/
- public static final SourceInfo UNKNOWN = new SourceInfo(0, 0, 0,
+ public static final SourceInfo UNKNOWN = new Immutable(0, 0, 0,
"Unknown source", true);
- /**
- * Examines the call stack to automatically determine a useful value to
- * provide as the caller argument to SourceInfo factory methods.
- */
- public static String findCaller() {
- /*
- * TODO(bobv): This function needs to be made robust for middle-man callers
- * other than JProgram and JsProgram.
- */
- String sourceInfoClassName = SourceInfo.class.getName();
- boolean wasInFindCaller = false;
-
- for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
- boolean inSourceInfo = e.getClassName().equals(SourceInfo.class.getName());
- if (inSourceInfo && "findCaller".equals(e.getMethodName())) {
- wasInFindCaller = true;
- } else if (wasInFindCaller
- && !"createSourceInfoSynthetic".equals(e.getMethodName())
- && !sourceInfoClassName.equals(e.getClassName())) {
- return e.getClassName() + "." + e.getMethodName();
- }
- }
-
- return "Unknown caller";
- }
-
+ private static final SourceInfo[] EMPTY_SOURCEINFO_ARRAY = new SourceInfo[0];
private final Set<SourceInfo> additionalAncestors = new HashSet<SourceInfo>();
private final String caller;
+ private final EnumMap<Axis, Correlation> correlations = new EnumMap<Axis, Correlation>(
+ Axis.class);
/**
- * This flag controls the behaviors of {@link #makeSynthetic(String)} and
- * {@link #makeChild(String)}.
+ * This flag controls the behavior of {@link #makeChild(String)}.
*/
private final boolean createDescendants;
private final int endPos;
- private final String fileName;
- private transient Reference<Collection<SourceInfo>> lazyRoots;
+ private transient Reference<Set<SourceInfo>> lazyRoots;
private final String mutation;
private final SourceInfo parent;
private final int startLine;
private final int startPos;
+ private final Set<SourceInfo> supertypeAncestors = new HashSet<SourceInfo>();
+
protected SourceInfo(int startPos, int endPos, int startLine,
String fileName, boolean createDescendants) {
+ assert fileName != null;
+
this.createDescendants = createDescendants;
this.startPos = startPos;
this.endPos = endPos;
this.startLine = startLine;
- this.fileName = fileName;
this.parent = null;
this.mutation = null;
this.caller = null;
+
+ // Don't use addCorrelation because of the immutable subclasses
+ Correlation file = Correlation.by(fileName);
+ correlations.put(file.getAxis(), file);
}
private SourceInfo(SourceInfo parent, String mutation, String caller,
SourceInfo... additionalAncestors) {
+ assert parent != null;
+ assert mutation != null;
+ assert caller != null;
+
this.createDescendants = parent.createDescendants;
this.startPos = parent.startPos;
this.endPos = parent.endPos;
this.startLine = parent.startLine;
- this.fileName = parent.fileName;
this.additionalAncestors.addAll(Arrays.asList(additionalAncestors));
this.additionalAncestors.addAll(parent.additionalAncestors);
this.parent = parent;
@@ -104,27 +152,141 @@
this.caller = caller;
}
- public synchronized void addAdditonalAncestors(SourceInfo... sourceInfos) {
- for (SourceInfo ancestor : sourceInfos) {
- if (ancestor == this) {
- continue;
- } else if (additionalAncestors.contains(ancestor)) {
- continue;
- } else {
- additionalAncestors.add(ancestor);
- }
+ /**
+ * Add additional ancestor SourceInfos. These SourceInfo objects indicate that
+ * a merge-type operation took place or that the additional ancestors have a
+ * containment relationship with the SourceInfo.
+ */
+ public void addAdditonalAncestors(SourceInfo... sourceInfos) {
+ if (!createDescendants) {
+ return;
}
+
+ additionalAncestors.addAll(Arrays.asList(sourceInfos));
+ additionalAncestors.remove(this);
+
if (lazyRoots != null) {
lazyRoots.clear();
}
}
+ /**
+ * Add a Correlation to the SourceInfo.
+ *
+ * @throws IllegalArgumentException if a Correlation with the same Axis had
+ * been previously added to the SourceInfo. The reason for this is
+ * that a Correlation shouldn't be re-applied to the same SourceInfo
+ * node, if this were done, the caller should have also called
+ * makeChild() first, since something interesting is gong on.
+ */
+ public void addCorrelation(Correlation c) {
+ if (!createDescendants) {
+ return;
+ }
+
+ Axis axis = c.getAxis();
+
+ if (correlations.containsKey(axis)) {
+ throw new IllegalArgumentException("Correlation on axis " + axis
+ + " has already been added. Call makeChild() first.");
+ }
+
+ correlations.put(axis, c);
+ }
+
+ /**
+ * Add SourceInfos that indicate the supertype derivation.
+ */
+ public void addSupertypeAncestors(SourceInfo... sourceInfos) {
+ if (!createDescendants) {
+ return;
+ }
+
+ supertypeAncestors.addAll(Arrays.asList(sourceInfos));
+ supertypeAncestors.remove(this);
+ }
+
+ /**
+ * Returns all Correlations applied to this SourceInfo, its parent, additional
+ * ancestor SourceInfo, and any supertype SourceInfos.
+ */
+ public Set<Correlation> getAllCorrelations() {
+ EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
+ Axis.class);
+ findCorrelations(accumulator, EnumSet.allOf(Axis.class), false);
+
+ Set<Correlation> toReturn = new HashSet<Correlation>();
+ for (Set<Correlation> toAdd : accumulator.values()) {
+ toReturn.addAll(toAdd);
+ }
+
+ return Collections.unmodifiableSet(toReturn);
+ }
+
+ /**
+ * Returns all Correlations along a given axis applied to this SourceInfo, its
+ * parent, additional ancestor SourceInfo, and any supertype SourceInfos.
+ */
+ public Set<Correlation> getAllCorrelations(Axis axis) {
+ EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
+ Axis.class);
+ findCorrelations(accumulator, EnumSet.of(axis), false);
+ assert accumulator.size() < 2;
+ if (accumulator.size() == 0) {
+ return Collections.unmodifiableSet(new HashSet<Correlation>());
+ } else {
+ assert accumulator.containsKey(axis);
+ assert accumulator.get(axis).size() > 0;
+ return Collections.unmodifiableSet(accumulator.get(axis));
+ }
+ }
+
public int getEndPos() {
return endPos;
}
public String getFileName() {
- return fileName;
+ return getPrimaryCorrelation(Axis.FILE).getIdent();
+ }
+
+ /**
+ * Return the most-derived Correlation along a given Axis or <code>null</code>
+ * if no such correlation exists. The search path uses the current SourceInfo,
+ * parent chain, and additional ancestors, but not supertype SourceInfos.
+ */
+ public Correlation getPrimaryCorrelation(Axis axis) {
+ EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
+ Axis.class);
+ findCorrelations(accumulator, EnumSet.of(axis), true);
+ assert accumulator.size() < 2;
+ if (accumulator.size() == 0) {
+ return null;
+ } else {
+ assert accumulator.containsKey(axis);
+ assert accumulator.get(axis).size() == 1;
+ return accumulator.get(axis).iterator().next();
+ }
+ }
+
+ /**
+ * Returns the most-derived Correlations along each Axis on which a
+ * Correlation has been set. The search path uses the current SourceInfo,
+ * parent chain, and additional ancestors, but not supertype SourceInfos.
+ */
+ public Set<Correlation> getPrimaryCorrelations() {
+ EnumMap<Axis, Set<Correlation>> accumulator = new EnumMap<Axis, Set<Correlation>>(
+ Axis.class);
+ findCorrelations(accumulator, EnumSet.allOf(Axis.class), true);
+
+ EnumMap<Axis, Correlation> toReturn = new EnumMap<Axis, Correlation>(
+ Axis.class);
+ for (Map.Entry<Axis, Set<Correlation>> entry : accumulator.entrySet()) {
+ assert entry.getValue().size() == 1;
+ toReturn.put(entry.getKey(), entry.getValue().iterator().next());
+ }
+
+ return Collections.unmodifiableSet(new HashSet<Correlation>(
+ toReturn.values()));
}
/**
@@ -132,11 +294,11 @@
* SourceInfo objects which were not derived from others, via
* {@link #makeChild}, will list itself as its root SourceInfo.
*/
- public synchronized Collection<SourceInfo> getRoots() {
- if (parent == null) {
+ public Set<SourceInfo> getRoots() {
+ if (parent == null && additionalAncestors.isEmpty()) {
// If parent is null, we shouldn't have additional ancestors
- assert additionalAncestors.size() == 0;
- return Collections.singleton(this);
+ return Collections.unmodifiableSet(new HashSet<SourceInfo>(
+ Collections.singleton(this)));
} else if (additionalAncestors.size() == 0) {
// This is a fairly typical case, where a node only has a parent
@@ -144,21 +306,26 @@
}
// See if previously-computed work is available
- Collection<SourceInfo> roots;
+ Set<SourceInfo> roots;
if (lazyRoots != null && (roots = lazyRoots.get()) != null) {
return roots;
}
// Otherwise, do some actual work
- roots = new ArrayList<SourceInfo>();
+ roots = new HashSet<SourceInfo>();
- roots.addAll(parent.getRoots());
+ if (parent == null) {
+ roots.add(this);
+ } else {
+ roots.addAll(parent.getRoots());
+ }
+
for (SourceInfo ancestor : additionalAncestors) {
roots.addAll(ancestor.getRoots());
}
- Collection<SourceInfo> toReturn = Collections.unmodifiableCollection(roots);
- lazyRoots = new SoftReference<Collection<SourceInfo>>(toReturn);
+ Set<SourceInfo> toReturn = Collections.unmodifiableSet(roots);
+ lazyRoots = new SoftReference<Set<SourceInfo>>(toReturn);
return toReturn;
}
@@ -187,19 +354,70 @@
}
}
- for (SourceInfo root : getRoots()) {
- toReturn.append(root.fileName + ":" + root.startLine + "\n");
+ Set<SourceInfo> roots = getRoots();
+ if (!roots.isEmpty()) {
+ toReturn.append("\nRoots:\n");
+ for (SourceInfo root : roots) {
+ toReturn.append(" " + root.getFileName() + ":" + root.getStartLine()
+ + "\n");
+ }
+ }
+
+ Set<Correlation> allCorrelations = getAllCorrelations();
+ if (!allCorrelations.isEmpty()) {
+ toReturn.append("\nCorrelations:\n");
+ for (Correlation c : allCorrelations) {
+ toReturn.append(" " + c + "\n");
+ }
+ }
+
+ Set<SourceInfo> supertypes = supertypeAncestors;
+ if (!supertypes.isEmpty()) {
+ toReturn.append("\nSupertypes:\n{\n");
+ for (SourceInfo info : supertypes) {
+ toReturn.append(info.getStory());
+ }
+ toReturn.append("\n}\n");
}
return toReturn.toString();
}
/**
+ * Returns <code>true</code> if {@link #getAllCorrelations()} would return a
+ * Correlation that has one or more of the specifies Axes.
+ */
+ public boolean hasCorrelation(Set<Axis> axes) {
+ // Try local information
+ if (!correlations.isEmpty()) {
+ for (Axis a : axes) {
+ if (correlations.containsKey(a)) {
+ return true;
+ }
+ }
+ }
+
+ // Try the parent chain
+ if (parent != null && parent.hasCorrelation(axes)) {
+ return true;
+ }
+
+ // Try additional ancestors
+ for (SourceInfo info : additionalAncestors) {
+ if (info.hasCorrelation(axes)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Create a derived SourceInfo object. If SourceInfo collection is disabled,
* this method will return the current object.
*/
public SourceInfo makeChild(String description) {
- return makeChild(description, new SourceInfo[0]);
+ return makeChild(description, EMPTY_SOURCEINFO_ARRAY);
}
/**
@@ -213,6 +431,50 @@
return this;
}
- return new SourceInfo(this, description, findCaller(), additionalAncestors);
+ // TODO : Force the caller to be passed in
+ return new SourceInfo(this, description, "Unrecorded caller",
+ additionalAncestors);
+ }
+
+ /**
+ * Implementation of the various getCorrelations functions.
+ */
+ private void findCorrelations(EnumMap<Axis, Set<Correlation>> accumulator,
+ EnumSet<Axis> filter, boolean derivedOnly) {
+ // Short circuit if all possible values have been seen
+ if (derivedOnly && accumulator.size() == filter.size()) {
+ return;
+ }
+
+ for (Map.Entry<Axis, Correlation> entry : correlations.entrySet()) {
+ Axis key = entry.getKey();
+ boolean containsKey = accumulator.containsKey(key);
+ Correlation value = entry.getValue();
+
+ if (containsKey) {
+ if (!derivedOnly) {
+ accumulator.get(key).add(value);
+ }
+ } else if (filter.contains(key)) {
+ Set<Correlation> set = new HashSet<Correlation>();
+ set.add(value);
+ accumulator.put(key, derivedOnly ? Collections.unmodifiableSet(set)
+ : set);
+ }
+ }
+
+ if (parent != null) {
+ parent.findCorrelations(accumulator, filter, derivedOnly);
+ }
+
+ for (SourceInfo info : additionalAncestors) {
+ info.findCorrelations(accumulator, filter, derivedOnly);
+ }
+
+ if (!derivedOnly) {
+ for (SourceInfo info : supertypeAncestors) {
+ info.findCorrelations(accumulator, filter, derivedOnly);
+ }
+ }
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index d4c81dc7..20c5f13 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -467,9 +467,8 @@
* itself.
*/
public SourceInfo createSourceInfoSynthetic(String description) {
- String caller = enableSourceInfoDescendants ? SourceInfoJava.findCaller()
- : "Unknown caller";
- return createSourceInfo(0, caller).makeChild(description);
+ // TODO : Force the caller to be passed in
+ return createSourceInfo(0, "Synthetic").makeChild(description);
}
public JReferenceType generalizeTypes(
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java b/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java
index b817334..07e4267 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/SourceInfoJava.java
@@ -21,13 +21,13 @@
* An implementation of SourceInfo representing SourceInfo nodes derived from
* the Java AST. Instances of this class should only be constructed by JProgram.
*/
-class SourceInfoJava extends SourceInfo {
+public class SourceInfoJava extends SourceInfo {
/**
* Indicates that an AST element is an intrinsic element of the AST and has no
* meaningful source location. This is typically used by singleton AST
* elements or for literal values.
*/
- public static final SourceInfo INTRINSIC = new SourceInfoJava(0, 0, 0,
+ public static final SourceInfo INTRINSIC = new Immutable(0, 0, 0,
"Java intrinsics", true);
/**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 67467d7..fd333b0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.dev.jjs.impl;
+import com.google.gwt.dev.jjs.Correlation;
+import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.JClassType;
@@ -147,10 +149,10 @@
return true;
}
- SourceInfo info = makeSourceInfo(argument);
+ JMethodBody enclosingBody = findEnclosingMethod(scope);
+ SourceInfo info = makeSourceInfo(argument, enclosingBody.getMethod());
LocalVariableBinding b = argument.binding;
JType localType = (JType) typeMap.get(b.type);
- JMethodBody enclosingBody = findEnclosingMethod(scope);
JLocal newLocal = program.createLocal(info, argument.name, localType,
b.isFinal(), enclosingBody);
typeMap.put(b, newLocal);
@@ -173,7 +175,7 @@
MethodBinding b = ctorDecl.binding;
JClassType enclosingType = (JClassType) typeMap.get(scope.enclosingSourceType());
String name = enclosingType.getShortName();
- SourceInfo info = makeSourceInfo(ctorDecl);
+ SourceInfo info = makeSourceInfo(ctorDecl, enclosingType);
JMethod newMethod = program.createMethod(info, name.toCharArray(),
enclosingType, enclosingType, false, false, true, b.isPrivate(),
false);
@@ -191,6 +193,8 @@
mapParameters(newMethod, ctorDecl);
// original params are now frozen
+ info.addCorrelation(Correlation.by(newMethod));
+
int syntheticParamCount = 0;
ReferenceBinding declaringClass = b.declaringClass;
if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
@@ -246,8 +250,8 @@
public boolean visit(FieldDeclaration fieldDeclaration, MethodScope scope) {
try {
FieldBinding b = fieldDeclaration.binding;
- SourceInfo info = makeSourceInfo(fieldDeclaration);
JReferenceType enclosingType = (JReferenceType) typeMap.get(scope.enclosingSourceType());
+ SourceInfo info = makeSourceInfo(fieldDeclaration, enclosingType);
Expression initialization = fieldDeclaration.initialization;
if (initialization != null
&& initialization instanceof AllocationExpression
@@ -268,7 +272,8 @@
LocalVariableBinding b = localDeclaration.binding;
JType localType = (JType) typeMap.get(localDeclaration.type.resolvedType);
JMethodBody enclosingBody = findEnclosingMethod(scope);
- SourceInfo info = makeSourceInfo(localDeclaration);
+ SourceInfo info = makeSourceInfo(localDeclaration,
+ enclosingBody.getMethod());
JLocal newLocal = program.createLocal(info, localDeclaration.name,
localType, b.isFinal(), enclosingBody);
typeMap.put(b, newLocal);
@@ -282,10 +287,11 @@
public boolean visit(MethodDeclaration methodDeclaration, ClassScope scope) {
try {
MethodBinding b = methodDeclaration.binding;
- SourceInfo info = makeSourceInfo(methodDeclaration);
JReferenceType enclosingType = (JReferenceType) typeMap.get(scope.enclosingSourceType());
+ SourceInfo info = makeSourceInfo(methodDeclaration, enclosingType);
JMethod newMethod = processMethodBinding(b, enclosingType, info);
mapParameters(newMethod, methodDeclaration);
+ info.addCorrelation(Correlation.by(newMethod));
if (newMethod.isNative()) {
processNativeMethod(methodDeclaration, info, enclosingType, newMethod);
@@ -318,6 +324,7 @@
JType type = (JType) typeMap.get(binding.type);
JField field = program.createEnumField(info, binding.name,
(JEnumType) enclosingType, (JClassType) type, binding.original().id);
+ info.addCorrelation(Correlation.by(field));
typeMap.put(binding, field);
return field;
}
@@ -346,16 +353,18 @@
JField field = program.createField(info, binding.name, enclosingType,
type, binding.isStatic(), disposition);
typeMap.put(binding, field);
+ info.addCorrelation(Correlation.by(field));
return field;
}
private JField createField(SyntheticArgumentBinding binding,
JReferenceType enclosingType) {
JType type = (JType) typeMap.get(binding.type);
- JField field = program.createField(
- enclosingType.getSourceInfo().makeChild(
- "Field " + String.valueOf(binding.name)), binding.name,
- enclosingType, type, false, Disposition.FINAL);
+ SourceInfo info = enclosingType.getSourceInfo().makeChild(
+ "Field " + String.valueOf(binding.name));
+ JField field = program.createField(info, binding.name, enclosingType,
+ type, false, Disposition.FINAL);
+ info.addCorrelation(Correlation.by(field));
if (binding.matchingField != null) {
typeMap.put(binding.matchingField, field);
}
@@ -366,7 +375,7 @@
private JParameter createParameter(LocalVariableBinding binding,
JMethod enclosingMethod) {
JType type = (JType) typeMap.get(binding.type);
- SourceInfo info = makeSourceInfo(binding.declaration);
+ SourceInfo info = makeSourceInfo(binding.declaration, enclosingMethod);
JParameter param = program.createParameter(info, binding.name, type,
binding.isFinal(), false, enclosingMethod);
typeMap.put(binding, param);
@@ -486,21 +495,36 @@
return (JMethodBody) method.getBody();
}
- private SourceInfo makeSourceInfo(AbstractMethodDeclaration methodDecl) {
+ private SourceInfo makeSourceInfo(AbstractMethodDeclaration methodDecl,
+ HasSourceInfo enclosing) {
CompilationResult compResult = methodDecl.compilationResult;
int[] indexes = compResult.lineSeparatorPositions;
String fileName = String.valueOf(compResult.fileName);
int startLine = Util.getLineNumber(methodDecl.sourceStart, indexes, 0,
indexes.length - 1);
- return program.createSourceInfo(methodDecl.sourceStart,
+ SourceInfo toReturn = program.createSourceInfo(methodDecl.sourceStart,
methodDecl.bodyEnd, startLine, fileName);
+
+ // The SourceInfo will inherit Correlations from its enclosing object
+ if (enclosing != null) {
+ toReturn.addAdditonalAncestors(enclosing.getSourceInfo());
+ }
+
+ return toReturn;
}
- private SourceInfo makeSourceInfo(Statement stmt) {
+ private SourceInfo makeSourceInfo(Statement stmt, HasSourceInfo enclosing) {
int startLine = Util.getLineNumber(stmt.sourceStart,
currentSeparatorPositions, 0, currentSeparatorPositions.length - 1);
- return program.createSourceInfo(stmt.sourceStart, stmt.sourceEnd,
- startLine, currentFileName);
+ SourceInfo toReturn = program.createSourceInfo(stmt.sourceStart,
+ stmt.sourceEnd, startLine, currentFileName);
+
+ // The SourceInfo will inherit Correlations from its enclosing object
+ if (enclosing != null) {
+ toReturn.addAdditonalAncestors(enclosing.getSourceInfo());
+ }
+
+ return toReturn;
}
private void mapParameters(JMethod method, AbstractMethodDeclaration x) {
@@ -588,6 +612,7 @@
assert (binding.superclass().isClass() || binding.superclass().isEnum());
JClassType superClass = (JClassType) typeMap.get(superClassBinding);
type.extnds = superClass;
+ type.getSourceInfo().addSupertypeAncestors(superClass.getSourceInfo());
}
ReferenceBinding[] superInterfaces = binding.superInterfaces();
@@ -596,6 +621,8 @@
assert (superInterfaceBinding.isInterface());
JInterfaceType superInterface = (JInterfaceType) typeMap.get(superInterfaceBinding);
type.implments.add(superInterface);
+ type.getSourceInfo().addSupertypeAncestors(
+ superInterface.getSourceInfo());
}
if (type instanceof JEnumType) {
@@ -765,7 +792,8 @@
} else {
ice = new InternalCompilerException("Error building type map", e);
}
- ice.addNode(amd.getClass().getName(), amd.toString(), makeSourceInfo(amd));
+ ice.addNode(amd.getClass().getName(), amd.toString(), makeSourceInfo(amd,
+ null));
return ice;
}
@@ -781,8 +809,8 @@
} else {
ice = new InternalCompilerException("Error building type map", e);
}
- ice.addNode(stmt.getClass().getName(), stmt.toString(),
- makeSourceInfo(stmt));
+ ice.addNode(stmt.getClass().getName(), stmt.toString(), makeSourceInfo(
+ stmt, null));
return ice;
}
}
@@ -872,6 +900,7 @@
assert (false);
return false;
}
+ info.addCorrelation(Correlation.by(newType));
/**
* We emulate static initializers and instance initializers as methods.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index d63a706..674df57 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.jjs.impl;
+import com.google.gwt.dev.jjs.Correlation;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
@@ -996,7 +997,6 @@
}
JExpression processExpression(FieldReference x) {
- SourceInfo info = makeSourceInfo(x);
FieldBinding fieldBinding = x.binding;
JField field;
if (fieldBinding.declaringClass == null) {
@@ -1008,6 +1008,7 @@
} else {
field = (JField) typeMap.get(fieldBinding);
}
+ SourceInfo info = makeSourceInfo(x);
JExpression instance = dispProcessExpression(x.receiver);
JExpression fieldRef = new JFieldRef(program, info, instance, field,
currentClass);
@@ -2092,7 +2093,8 @@
"FieldRef referencing field in a different type.");
}
}
- return new JFieldRef(program, info, instance, field, currentClass);
+ return new JFieldRef(program, info.makeChild("Reference",
+ variable.getSourceInfo()), instance, field, currentClass);
}
throw new InternalCompilerException("Unknown JVariable subclass.");
}
@@ -2239,8 +2241,15 @@
private SourceInfo makeSourceInfo(Statement x) {
int startLine = Util.getLineNumber(x.sourceStart,
currentSeparatorPositions, 0, currentSeparatorPositions.length - 1);
- return program.createSourceInfo(x.sourceStart, x.sourceEnd, startLine,
- currentFileName);
+ SourceInfo toReturn = program.createSourceInfo(x.sourceStart,
+ x.sourceEnd, startLine, currentFileName);
+ if (currentClass != null) {
+ toReturn.addCorrelation(Correlation.by(currentClass));
+ }
+ if (currentMethod != null) {
+ toReturn.addCorrelation(Correlation.by(currentMethod));
+ }
+ return toReturn;
}
private JExpression maybeCast(JType expected, JExpression expression) {
@@ -2410,6 +2419,7 @@
if (!method.overrides.contains(upRef)) {
method.overrides.add(upRef);
+ method.getSourceInfo().addSupertypeAncestors(upRef.getSourceInfo());
break;
}
}
@@ -2440,6 +2450,8 @@
JMethod upRef = (JMethod) typeMap.get(tryMethod);
if (!method.overrides.contains(upRef)) {
method.overrides.add(upRef);
+ method.getSourceInfo().addSupertypeAncestors(
+ upRef.getSourceInfo());
break;
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 8224714..bbdbfb3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.jjs.impl;
+import com.google.gwt.dev.jjs.Correlation;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JsOutputOption;
import com.google.gwt.dev.jjs.SourceInfo;
@@ -316,10 +317,12 @@
jsFunction.setName(globalName);
} else {
// create a new peer JsFunction
- jsFunction = new JsFunction(x.getSourceInfo(), topScope, globalName,
- true);
+ SourceInfo sourceInfo = x.getSourceInfo().makeChild(
+ "Translated JS function");
+ jsFunction = new JsFunction(sourceInfo, topScope, globalName, true);
methodBodyMap.put(x.getBody(), jsFunction);
}
+ jsFunction.getSourceInfo().addCorrelation(Correlation.by(jsFunction));
push(jsFunction.getScope());
return true;
}
@@ -383,8 +386,8 @@
private JsFunction[] entryFunctions;
/**
- * A reverse index for {@link JProgram#entryMethods}. Each entry method
- * is mapped to its integer index.
+ * A reverse index for {@link JProgram#entryMethods}. Each entry method is
+ * mapped to its integer index.
*/
private Map<JMethod, Integer> entryMethodToIndex;
@@ -1311,6 +1314,7 @@
gwtOnLoadName.setObfuscatable(false);
JsFunction gwtOnLoad = new JsFunction(sourceInfo, topScope,
gwtOnLoadName, true);
+ sourceInfo.addCorrelation(Correlation.by(gwtOnLoad));
globalStmts.add(gwtOnLoad.makeStmt());
JsBlock body = new JsBlock(sourceInfo);
gwtOnLoad.setBody(body);
@@ -1370,6 +1374,7 @@
SourceInfo sourceInfo = jsProgram.createSourceInfoSynthetic("Null function");
JsFunction nullFunc = new JsFunction(sourceInfo, topScope,
nullMethodName, true);
+ sourceInfo.addCorrelation(Correlation.by(nullFunc));
nullFunc.setBody(new JsBlock(sourceInfo));
globalStatements.add(nullFunc.makeStmt());
}
@@ -1385,6 +1390,8 @@
// function com_example_foo_Foo() { }
JsFunction seedFunc = new JsFunction(sourceInfo, topScope,
seedFuncName, true);
+ seedFuncName.setStaticRef(seedFunc);
+ sourceInfo.addCorrelation(Correlation.by(seedFunc));
JsBlock body = new JsBlock(sourceInfo);
seedFunc.setBody(body);
globalStmts.add(seedFunc.makeStmt());
@@ -1396,8 +1403,13 @@
JsExpression rhs;
if (x.extnds != null) {
JsNew newExpr = new JsNew(sourceInfo);
- newExpr.setConstructorExpression(names.get(x.extnds).makeRef(
- sourceInfo));
+ JsNameRef superPrototypeRef = names.get(x.extnds).makeRef(sourceInfo);
+ newExpr.setConstructorExpression(superPrototypeRef);
+ JsNode<?> staticRef = superPrototypeRef.getName().getStaticRef();
+ if (staticRef != null) {
+ seedFunc.getSourceInfo().addSupertypeAncestors(
+ staticRef.getSourceInfo());
+ }
rhs = newExpr;
} else {
rhs = new JsObjectLiteral(sourceInfo);
diff --git a/dev/core/src/com/google/gwt/dev/js/JsParser.java b/dev/core/src/com/google/gwt/dev/js/JsParser.java
index 446ad6d..8228842 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsParser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsParser.java
@@ -79,8 +79,9 @@
public class JsParser {
private JsProgram program;
+ private SourceInfo rootSourceInfo = SourceInfo.UNKNOWN;
private final Stack<JsScope> scopeStack = new Stack<JsScope>();
- private SourceInfo sourceInfo = SourceInfo.UNKNOWN;
+ private final Stack<SourceInfo> sourceInfoStack = new Stack<SourceInfo>();
public JsParser() {
// Create a custom error handler so that we can throw our own exceptions.
@@ -116,7 +117,7 @@
// Map the Rhino AST to ours.
//
program = scope.getProgram();
- pushScope(scope);
+ pushScope(scope, rootSourceInfo);
List<JsStatement> stmts = mapStatements(topNode);
popScope();
@@ -139,7 +140,7 @@
* Set the base SourceInfo object to use when creating new JS AST nodes.
*/
public void setSourceInfo(SourceInfo sourceInfo) {
- this.sourceInfo = sourceInfo;
+ rootSourceInfo = sourceInfo;
}
private JsParserException createParserException(String msg, Node offender) {
@@ -153,8 +154,11 @@
}
private SourceInfo makeSourceInfo(Node node) {
- return program.createSourceInfo(node.getLineno()
- + sourceInfo.getStartLine() + 1, sourceInfo.getFileName());
+ SourceInfo parent = sourceInfoStack.peek();
+ SourceInfo toReturn = program.createSourceInfo(node.getLineno()
+ + parent.getStartLine() + 1, parent.getFileName());
+ toReturn.addAdditonalAncestors(parent);
+ return toReturn;
}
private JsNode<?> map(Node node) throws JsParserException {
@@ -237,8 +241,8 @@
return mapName(node);
case TokenStream.STRING:
- return program.getStringLiteral(
- sourceInfo.makeChild("JS String literal"), node.getString());
+ return program.getStringLiteral(sourceInfoStack.peek().makeChild(
+ "JS String literal"), node.getString());
case TokenStream.NUMBER:
return mapNumber(node);
@@ -671,7 +675,7 @@
// Creating a function also creates a new scope, which we push onto
// the scope stack.
//
- pushScope(toFn.getScope());
+ pushScope(toFn.getScope(), fnSourceInfo);
while (fromParamNode != null) {
String fromParamName = fromParamNode.getString();
@@ -1126,7 +1130,7 @@
// Map the catch body.
//
Node fromCatchBody = fromCondition.getNext();
- pushScope(catchBlock.getScope());
+ pushScope(catchBlock.getScope(), catchBlock.getSourceInfo());
catchBlock.setBody(mapBlock(fromCatchBody));
popScope();
@@ -1208,9 +1212,11 @@
private void popScope() {
scopeStack.pop();
+ sourceInfoStack.pop();
}
- private void pushScope(JsScope scope) {
+ private void pushScope(JsScope scope, SourceInfo sourceInfo) {
scopeStack.push(scope);
+ sourceInfoStack.push(sourceInfo);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java b/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java
index 9ba532c..1329035 100644
--- a/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java
+++ b/dev/core/src/com/google/gwt/dev/js/SourceInfoHistogram.java
@@ -15,13 +15,18 @@
*/
package com.google.gwt.dev.js;
+import com.google.gwt.dev.jjs.Correlation;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.SourceInfo;
-import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.Correlation.Axis;
import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsValueLiteral;
import com.google.gwt.dev.js.ast.JsVisitable;
+import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.HtmlTextOutput;
import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.dev.util.Util;
@@ -29,25 +34,77 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.Comparator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.SortedMap;
+import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
+import java.util.TreeSet;
/**
* This is a test reporting visitor for SOYC experiments. It will likely
* disappear once a proper export format and viewer application are written.
*/
public class SourceInfoHistogram {
- /**
- * Stub; unused and will probably be discarded.
- */
- public static class HistogramData {
- // Use weak references to AST nodes. If the AST node gets pruned, we won't
- // need it either
+ private static class DependencyReportVisitor extends JsVisitor {
+ private final Map<Correlation, Set<Correlation>> deps = new TreeMap<Correlation, Set<Correlation>>(Correlation.AXIS_IDENT_COMPARATOR);
+ private final Stack<HasSourceInfo> currentContext = new Stack<HasSourceInfo>();
+
+ @Override
+ protected <T extends JsVisitable<T>> T doAccept(T node) {
+ /*
+ * The casts to Object here are because javac 1.5.0_16 doesn't think T
+ * could ever be coerced to JsNode.
+ */
+ boolean createScope = ((Object) node) instanceof JsProgram
+ || ((Object) node) instanceof JsFunction;
+
+ if (createScope) {
+ currentContext.push((HasSourceInfo) node);
+ }
+
+ // JsValueLiterals are shared AST nodes and distort dependency info
+ if (!(((Object) node) instanceof JsValueLiteral)
+ && !currentContext.isEmpty()) {
+ Set<Correlation> toAdd = ((HasSourceInfo) node).getSourceInfo().getAllCorrelations();
+
+ HasSourceInfo context = currentContext.peek();
+ for (Correlation c : context.getSourceInfo().getAllCorrelations()) {
+ Set<Correlation> set = deps.get(c);
+ if (set == null) {
+ deps.put(c, set = new TreeSet<Correlation>(Correlation.AXIS_IDENT_COMPARATOR));
+ }
+ set.addAll(toAdd);
+ }
+ }
+
+ T toReturn = super.doAccept(node);
+ if (createScope) {
+ currentContext.pop();
+ }
+
+ return toReturn;
+ }
+
+ @Override
+ protected <T extends JsVisitable<T>> void doAcceptList(List<T> collection) {
+ for (T node : collection) {
+ doAccept(node);
+ }
+ }
+
+ @Override
+ protected <T extends JsVisitable<T>> void doAcceptWithInsertRemove(
+ List<T> collection) {
+ for (T node : collection) {
+ doAccept(node);
+ }
+ }
}
private static class HSVUtils {
@@ -141,71 +198,12 @@
}
}
- private static class HtmlTextOutput implements TextOutput {
- private final DefaultTextOutput out;
-
- public HtmlTextOutput(boolean compact) {
- out = new DefaultTextOutput(compact);
- }
-
- public void indentIn() {
- out.indentIn();
- }
-
- public void indentOut() {
- out.indentOut();
- }
-
- public void newline() {
- out.newline();
- }
-
- public void newlineOpt() {
- out.newlineOpt();
- }
-
- public void print(char c) {
- print(String.valueOf(c));
- }
-
- public void print(char[] s) {
- print(String.valueOf(s));
- }
-
- public void print(String s) {
- out.print(Util.escapeXml(s));
- }
-
- public void printOpt(char c) {
- printOpt(String.valueOf(c));
- }
-
- public void printOpt(char[] s) {
- printOpt(String.valueOf(s));
- }
-
- public void printOpt(String s) {
- out.printOpt(Util.escapeXml(s));
- }
-
- public void printRaw(String s) {
- out.print(s);
- }
-
- @Override
- public String toString() {
- return out.toString();
- }
- }
-
private static class JavaNormalReportVisitor extends
JsSourceGenerationVisitor {
Stack<HasSourceInfo> stack = new Stack<HasSourceInfo>();
int total = 0;
- Map<String, Integer> totalsByFileName = new HashMap<String, Integer>();
-
- private final SortedMap<SourceInfo, StringBuilder> infoToSource = new TreeMap<SourceInfo, StringBuilder>(
- SOURCE_INFO_COMPARATOR);
+ private final Map<Correlation, StringBuilder> sourceByCorrelation = new TreeMap<Correlation, StringBuilder>(Correlation.AXIS_IDENT_COMPARATOR);
+ private final Map<Correlation, StringBuilder> sourceByAllCorrelation = new TreeMap<Correlation, StringBuilder>(Correlation.AXIS_IDENT_COMPARATOR);
private final SwitchTextOutput out;
public JavaNormalReportVisitor(SwitchTextOutput out) {
@@ -228,6 +226,9 @@
out.begin();
}
return super.doAccept(node);
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ throw e;
} finally {
if (openContext) {
int count = commit((HasSourceInfo) node, false);
@@ -258,28 +259,40 @@
}
private void accumulateTotal(SourceInfo sourceInfo, int count) {
- for (SourceInfo root : sourceInfo.getRoots()) {
- String fileName = root.getFileName();
- Integer sourceTotal = totalsByFileName.get(fileName);
- if (sourceTotal == null) {
- totalsByFileName.put(fileName, count);
- } else {
- totalsByFileName.put(fileName, sourceTotal + count);
- }
- }
total += count;
}
private int commit(HasSourceInfo x, boolean expectMore) {
- StringBuilder builder = infoToSource.get(x.getSourceInfo());
- if (builder == null) {
- builder = new StringBuilder();
- infoToSource.put(x.getSourceInfo(), builder);
+ SourceInfo info = x.getSourceInfo();
+ List<StringBuilder> builders = new ArrayList<StringBuilder>();
+
+ // This should be an accurate count
+ for (Correlation c : info.getPrimaryCorrelations()) {
+ StringBuilder builder = sourceByCorrelation.get(c);
+ if (builder == null) {
+ builder = new StringBuilder();
+ sourceByCorrelation.put(c, builder);
+ }
+ builders.add(builder);
}
+
+ /*
+ * This intentionally overcounts base classes, methods in order to show
+ * aggregate based on subtypes.
+ */
+ for (Correlation c : info.getAllCorrelations()) {
+ StringBuilder builder = sourceByAllCorrelation.get(c);
+ if (builder == null) {
+ builder = new StringBuilder();
+ sourceByAllCorrelation.put(c, builder);
+ }
+ builders.add(builder);
+ }
+
if (expectMore) {
- return out.flush(builder);
+ return out.flush(builders);
} else {
- return out.commit(builder);
+ return out.commit(builders);
}
}
}
@@ -299,7 +312,7 @@
if (node instanceof HasSourceInfo) {
SourceInfo info = ((HasSourceInfo) node).getSourceInfo();
openNode = context.isEmpty()
- || SOURCE_INFO_COMPARATOR.compare(context.peek(), info) != 0;
+ || SourceInfo.LOCATION_COMPARATOR.compare(context.peek(), info) != 0;
if (openNode) {
String color;
if (context.contains(info)) {
@@ -349,14 +362,16 @@
outs.push(new DefaultTextOutput(true));
}
- public int commit(StringBuilder build) {
+ public int commit(Collection<StringBuilder> builders) {
String string = outs.pop().toString();
- build.append(string);
+ for (StringBuilder builder : builders) {
+ builder.append(string);
+ }
return string.length();
}
- public int flush(StringBuilder build) {
- int toReturn = commit(build);
+ public int flush(Collection<StringBuilder> builders) {
+ int toReturn = commit(builders);
begin();
return toReturn;
}
@@ -402,73 +417,113 @@
}
}
- private static final Comparator<SourceInfo> SOURCE_INFO_COMPARATOR = new Comparator<SourceInfo>() {
- public int compare(SourceInfo o1, SourceInfo o2) {
- int toReturn = o1.getFileName().compareTo(o2.getFileName());
- if (toReturn != 0) {
- return toReturn;
- }
-
- toReturn = o1.getStartLine() - o2.getStartLine();
- if (toReturn != 0) {
- return toReturn;
- }
-
- // TODO need a counter in SourceInfos
- return o1.getStory().compareTo(o2.getStory());
- }
- };
-
- public static HistogramData exec(JProgram program) {
- return new HistogramData();
- }
-
- public static void exec(JsProgram program, HistogramData data,
- String outputPath) {
+ public static void exec(JsProgram program, String outputPath) {
+ writeDependencyReport(program, outputPath);
writeJavaNormalReport(program, outputPath);
writeJsNormalReport(program, outputPath);
}
+ private static void writeDependencyReport(JsProgram program, String outputPath) {
+ DependencyReportVisitor v = new DependencyReportVisitor();
+ v.accept(program);
+
+ Map<Correlation, Integer> idents = new HashMap<Correlation, Integer>();
+ TreeMap<String, Set<Integer>> clusters = new TreeMap<String, Set<Integer>>();
+ for (Correlation c : v.deps.keySet()) {
+ if (c.getAxis().equals(Axis.CLASS)) {
+ clusters.put(c.getIdent(), new TreeSet<Integer>());
+ }
+ }
+
+ EnumSet<Axis> toShow = EnumSet.of(Axis.METHOD, Axis.FIELD);
+
+ StringBuilder edges = new StringBuilder();
+ StringBuilder nodes = new StringBuilder();
+ StringBuilder subgraphs = new StringBuilder();
+
+ for (Map.Entry<Correlation, Set<Correlation>> entry : v.deps.entrySet()) {
+ Correlation key = entry.getKey();
+
+ if (!toShow.contains(key.getAxis())
+ || key.getIdent().startsWith("java.lang")) {
+ continue;
+ }
+
+ Set<Integer> keyClusterSet;
+ if (!idents.containsKey(key)) {
+ idents.put(key, idents.size());
+ nodes.append(idents.get(key) + " [label=\"" + key + "\"];\n");
+ }
+ if (key.getAxis().isJava()) {
+ keyClusterSet = clusters.get(clusters.headMap(key.getIdent()).lastKey());
+ keyClusterSet.add(idents.get(key));
+ } else {
+ keyClusterSet = null;
+ }
+
+ for (Correlation c : entry.getValue()) {
+ if (!toShow.contains(c.getAxis())
+ || c.getIdent().startsWith("java.lang")) {
+ continue;
+ }
+
+ Set<Integer> cClusterSet;
+ if (!idents.containsKey(c)) {
+ idents.put(c, idents.size());
+ nodes.append(idents.get(c) + " [label=\"" + c + "\"];\n");
+ }
+ if (c.getAxis().isJava()) {
+ cClusterSet = clusters.get(clusters.headMap(c.getIdent()).lastKey());
+ cClusterSet.add(idents.get(c));
+ } else {
+ cClusterSet = null;
+ }
+
+ edges.append(idents.get(key) + " -> " + idents.get(c));
+ if (keyClusterSet == cClusterSet) {
+ edges.append(" constraint=false");
+ }
+ edges.append(";\n");
+ }
+ }
+ int clusterNumber = 0;
+ for (Map.Entry<String, Set<Integer>> entry : clusters.entrySet()) {
+ Set<Integer> set = entry.getValue();
+ if (set.isEmpty()) {
+ continue;
+ }
+
+ subgraphs.append("subgraph cluster" + clusterNumber++ + " {");
+ subgraphs.append("label=\"" + entry.getKey() + "\";");
+ for (Integer i : set) {
+ subgraphs.append(i + "; ");
+ }
+ subgraphs.append("};\n");
+ }
+
+ PrintWriter out;
+ try {
+ File outputPathDir = new File(outputPath);
+ outputPathDir.mkdirs();
+ out = new PrintWriter(new FileWriter(File.createTempFile("soyc",
+ "-deps.dot", outputPathDir)));
+ } catch (IOException e) {
+ out = null;
+ }
+
+ out.println("digraph soyc {");
+ out.println(subgraphs.toString());
+ out.println(nodes.toString());
+ out.println(edges.toString());
+ out.println("}");
+ out.close();
+ }
+
private static void writeJavaNormalReport(JsProgram program, String outputPath) {
JavaNormalReportVisitor v = new JavaNormalReportVisitor(
new SwitchTextOutput());
v.accept(program);
- // Concatenate the per-SourceInfo data into per-file contents
- Map<String, StringBuffer> contentsByFile = new TreeMap<String, StringBuffer>();
- for (Map.Entry<SourceInfo, StringBuilder> contents : v.infoToSource.entrySet()) {
- SourceInfo sourceInfo = contents.getKey();
- String currentFile = sourceInfo.getFileName();
- StringBuffer buffer = contentsByFile.get(currentFile);
- if (buffer == null) {
- buffer = new StringBuffer();
- contentsByFile.put(currentFile, buffer);
- buffer.append("<div class=\"fileHeader\">\n");
- buffer.append(Util.escapeXml(String.format("%s : %2.1f%%", currentFile,
- (100.0 * v.totalsByFileName.get(currentFile) / v.total))));
- buffer.append("</div>\n");
- }
-
- buffer.append("<div class=\"jsLine\">");
- buffer.append("<div class=\"story\">");
- buffer.append(Util.escapeXml(sourceInfo.getStory()));
- buffer.append("</div>");
- buffer.append(Util.escapeXml(contents.getValue().toString()));
- buffer.append("</div>\n");
- }
-
- // Order the contents based on file size
- Map<Integer, StringBuffer> orderedContents = new TreeMap<Integer, StringBuffer>();
- for (Map.Entry<String, StringBuffer> entry : contentsByFile.entrySet()) {
- int size = -v.totalsByFileName.get(entry.getKey());
- StringBuffer appendTo = orderedContents.get(size);
- if (appendTo != null) {
- appendTo.append(entry.getValue());
- } else {
- orderedContents.put(size, entry.getValue());
- }
- }
-
PrintWriter out;
try {
File outputPathDir = new File(outputPath);
@@ -484,7 +539,7 @@
+ "* {font-family: monospace;}"
+ ".file {clear: both;}"
+ ".file * {display: none;}"
- + ".file .fileHeader {display: block; cursor: pointer;}"
+ + ".file .fileHeader, .file .fileHeader * {display: block; cursor: pointer;}"
+ ".fileOpen .fileHeader {clear: both;}"
+ ".fileOpen .javaLine {clear: both; float: left; white-space: pre; background: #efe;}"
+ ".fileOpen .jsLine {outline: thin solid black; float: right; clear: right; white-space: pre; background: #ddd;}"
@@ -495,10 +550,51 @@
out.println("</head><body>");
out.println(String.format("<h1>Total bytes: %d</h1>", v.total));
- for (StringBuffer buffer : orderedContents.values()) {
+ Map<Axis, Integer> totalsByAxis = new EnumMap<Axis, Integer>(Axis.class);
+ for (Map.Entry<Correlation, StringBuilder> entry : v.sourceByCorrelation.entrySet()) {
+ Correlation c = entry.getKey();
+ StringBuilder builder = entry.getValue();
+ int count = builder.length();
out.println("<div class=\"file\" onclick=\"this.className=(this.className=='file'?'fileOpen':'file')\">");
- out.println(buffer.toString());
- out.println("</div>");
+ out.println("<div class=\"fileHeader\">" + Util.escapeXml(c.toString())
+ + " : " + count + "</div>");
+ out.print("<div class=\"jsLine\">");
+ out.print(Util.escapeXml(builder.toString()));
+ out.print("</div></div>");
+
+ Axis axis = c.getAxis();
+ Integer t = totalsByAxis.get(axis);
+ if (t == null) {
+ totalsByAxis.put(axis, count);
+ } else {
+ totalsByAxis.put(axis, t + count);
+ }
+ }
+
+ out.println("<h1>Axis totals</h1>");
+ for (Map.Entry<Axis, Integer> entry : totalsByAxis.entrySet()) {
+ out.println("<div>" + entry.getKey() + " : " + entry.getValue()
+ + "</div>");
+ }
+
+ out.println("<h1>Cost of polymorphism</h1>");
+ for (Map.Entry<Correlation, StringBuilder> entry : v.sourceByAllCorrelation.entrySet()) {
+ Correlation c = entry.getKey();
+ StringBuilder builder = entry.getValue();
+ int count = builder.length();
+
+ StringBuilder uniqueOutput = v.sourceByCorrelation.get(c);
+ int uniqueCount = uniqueOutput == null ? 0 : uniqueOutput.length();
+ boolean bold = count != uniqueCount;
+
+ out.println("<div class=\"file\" onclick=\"this.className=(this.className=='file'?'fileOpen':'file')\">");
+ out.println("<div class=\"fileHeader\">" + (bold ? "<b>" : "")
+ + Util.escapeXml(c.toString()) + " : " + count + " versus "
+ + uniqueCount + "(" + (count - uniqueCount) + ")"
+ + (bold ? "</b>" : "") + "</div>");
+ out.print("<div class=\"jsLine\">");
+ out.print(Util.escapeXml(builder.toString()));
+ out.print("</div></div>");
}
out.println("<h1>Done</h1>");
@@ -507,9 +603,6 @@
}
private static void writeJsNormalReport(JsProgram program, String outputPath) {
- HtmlTextOutput htmlOut = new HtmlTextOutput(false);
- JsNormalReportVisitor v = new JsNormalReportVisitor(htmlOut);
- v.accept(program);
PrintWriter out;
try {
@@ -529,7 +622,11 @@
+ " position: relative; border-left: 8px solid white; z-index: 1;}"
+ "</style>");
out.println("</head><body>");
- out.println(htmlOut.toString());
+
+ HtmlTextOutput htmlOut = new HtmlTextOutput(out, false);
+ JsNormalReportVisitor v = new JsNormalReportVisitor(htmlOut);
+ v.accept(program);
+
out.println("</body></html>");
out.close();
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
index 42466fc..aa49818 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
@@ -23,13 +23,15 @@
public final class JsNameRef extends JsExpression implements CanBooleanEval,
HasName {
+ private boolean hasStaticRef;
private String ident;
private JsName name;
private JsExpression qualifier;
public JsNameRef(SourceInfo sourceInfo, JsName name) {
- super(sourceInfo);
+ super(sourceInfo.makeChild("Reference"));
this.name = name;
+ maybeUpdateSourceInfo();
}
public JsNameRef(SourceInfo sourceInfo, String ident) {
@@ -54,6 +56,11 @@
}
@Override
+ public SourceInfo getSourceInfo() {
+ return maybeUpdateSourceInfo();
+ }
+
+ @Override
public boolean hasSideEffects() {
if (qualifier == null) {
return false;
@@ -117,4 +124,22 @@
}
v.endVisit(this, ctx);
}
+
+ /**
+ * This corrects the JsNameRef's SourceInfo derivation when the JsName is
+ * created with a JsName that has not yet had its static reference set. This
+ * is the case in GenerateJavaScriptAST after the names and scopes visitor has
+ * been run, but before the AST is fully realized.
+ */
+ private SourceInfo maybeUpdateSourceInfo() {
+ SourceInfo toReturn = super.getSourceInfo();
+ if (!hasStaticRef && name != null) {
+ JsNode<?> staticRef = name.getStaticRef();
+ if (staticRef != null) {
+ toReturn.addAdditonalAncestors(name.getStaticRef().getSourceInfo());
+ hasStaticRef = true;
+ }
+ }
+ return toReturn;
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
index a649e4c..511587b 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -71,9 +71,8 @@
}
public SourceInfo createSourceInfoSynthetic(String description) {
- String caller = enableSourceInfoDescendants ? SourceInfoJs.findCaller()
- : "Unknown caller";
- return createSourceInfo(0, caller).makeChild(description);
+ // TODO : Force the caller to be passed in
+ return createSourceInfo(0, "Synthetic").makeChild(description);
}
public JsBooleanLiteral getBooleanLiteral(boolean truth) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java b/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java
index a465edb..9db902d 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/SourceInfoJs.java
@@ -28,7 +28,7 @@
* meaningful source location. This is typically used by singleton AST
* elements or for literal values.
*/
- public static final SourceInfo INTRINSIC = new SourceInfoJs(0, 0, 0,
+ public static final SourceInfo INTRINSIC = new Immutable(0, 0, 0,
"Js intrinsics", true);
/**
diff --git a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
new file mode 100644
index 0000000..0aa54c2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java
@@ -0,0 +1,119 @@
+/*
+ * 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.dev.util;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * An abstract base type to build TextOutput implementations.
+ */
+public abstract class AbstractTextOutput implements TextOutput {
+ private final boolean compact;
+ private int identLevel = 0;
+ private int indentGranularity = 2;
+ private char[][] indents = new char[][] {new char[0]};
+ private boolean justNewlined;
+ private PrintWriter out;
+
+ protected AbstractTextOutput(boolean compact) {
+ this.compact = compact;
+ }
+
+ public void indentIn() {
+ ++identLevel;
+ if (identLevel >= indents.length) {
+ // Cache a new level of indentation string.
+ //
+ char[] newIndentLevel = new char[identLevel * indentGranularity];
+ Arrays.fill(newIndentLevel, ' ');
+ char[][] newIndents = new char[indents.length + 1][];
+ System.arraycopy(indents, 0, newIndents, 0, indents.length);
+ newIndents[identLevel] = newIndentLevel;
+ indents = newIndents;
+ }
+ }
+
+ public void indentOut() {
+ --identLevel;
+ }
+
+ public void newline() {
+ if (compact) {
+ out.print('\n');
+ } else {
+ out.println();
+ }
+ justNewlined = true;
+ }
+
+ public void newlineOpt() {
+ if (!compact) {
+ out.println();
+ justNewlined = true;
+ }
+ }
+
+ public void print(char c) {
+ maybeIndent();
+ out.print(c);
+ justNewlined = false;
+ }
+
+ public void print(char[] s) {
+ maybeIndent();
+ out.print(s);
+ justNewlined = false;
+ }
+
+ public void print(String s) {
+ maybeIndent();
+ out.print(s);
+ justNewlined = false;
+ }
+
+ public void printOpt(char c) {
+ if (!compact) {
+ maybeIndent();
+ out.print(c);
+ }
+ }
+
+ public void printOpt(char[] s) {
+ if (!compact) {
+ maybeIndent();
+ out.print(s);
+ }
+ }
+
+ public void printOpt(String s) {
+ if (!compact) {
+ maybeIndent();
+ out.print(s);
+ }
+ }
+
+ protected void setPrintWriter(PrintWriter out) {
+ this.out = out;
+ }
+
+ private void maybeIndent() {
+ if (justNewlined && !compact) {
+ out.print(indents[identLevel]);
+ justNewlined = false;
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/DefaultTextOutput.java b/dev/core/src/com/google/gwt/dev/util/DefaultTextOutput.java
index 5f469e6..f57a123 100644
--- a/dev/core/src/com/google/gwt/dev/util/DefaultTextOutput.java
+++ b/dev/core/src/com/google/gwt/dev/util/DefaultTextOutput.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * 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
@@ -17,109 +17,26 @@
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.Arrays;
/**
- * Adapts {@link TextOutput} to a print writer.
+ * Adapts {@link TextOutput} to an internal text buffer.
*/
-public final class DefaultTextOutput implements TextOutput {
+public class DefaultTextOutput extends AbstractTextOutput {
- private final boolean compact;
- private int identLevel = 0;
- private int indentGranularity = 2;
- private char[][] indents = new char[][] {new char[0]};
- private boolean justNewlined;
- private final PrintWriter p;
- private final StringWriter sw;
+ private final StringWriter sw = new StringWriter();
+ private final PrintWriter out;
public DefaultTextOutput(boolean compact) {
- this.compact = compact;
- sw = new StringWriter();
- p = new PrintWriter(sw, false);
- }
-
- public void indentIn() {
- ++identLevel;
- if (identLevel >= indents.length) {
- // Cache a new level of indentation string.
- //
- char[] newIndentLevel = new char[identLevel * indentGranularity];
- Arrays.fill(newIndentLevel, ' ');
- char[][] newIndents = new char[indents.length + 1][];
- System.arraycopy(indents, 0, newIndents, 0, indents.length);
- newIndents[identLevel] = newIndentLevel;
- indents = newIndents;
- }
- }
-
- public void indentOut() {
- --identLevel;
- }
-
- public void newline() {
- if (compact) {
- p.print('\n');
- } else {
- p.println();
- }
- justNewlined = true;
- }
-
- public void newlineOpt() {
- if (!compact) {
- p.println();
- justNewlined = true;
- }
- }
-
- public void print(char c) {
- maybeIndent();
- p.print(c);
- justNewlined = false;
- }
-
- public void print(char[] s) {
- maybeIndent();
- p.print(s);
- justNewlined = false;
- }
-
- public void print(String s) {
- maybeIndent();
- p.print(s);
- justNewlined = false;
- }
-
- public void printOpt(char c) {
- if (!compact) {
- maybeIndent();
- p.print(c);
- }
- }
-
- public void printOpt(char[] s) {
- if (!compact) {
- maybeIndent();
- p.print(s);
- }
- }
-
- public void printOpt(String s) {
- if (!compact) {
- maybeIndent();
- p.print(s);
- }
+ super(compact);
+ setPrintWriter(out = new PrintWriter(sw));
}
public String toString() {
- p.flush();
- return sw.toString();
- }
-
- private void maybeIndent() {
- if (justNewlined && !compact) {
- p.print(indents[identLevel]);
- justNewlined = false;
+ out.flush();
+ if (sw != null) {
+ return sw.toString();
+ } else {
+ return super.toString();
}
}
}
diff --git a/dev/core/src/com/google/gwt/dev/util/HtmlTextOutput.java b/dev/core/src/com/google/gwt/dev/util/HtmlTextOutput.java
new file mode 100644
index 0000000..32b8a2e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/HtmlTextOutput.java
@@ -0,0 +1,72 @@
+/*
+ * 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.dev.util;
+
+import java.io.PrintWriter;
+
+/**
+ * An implementation of TextOutput that will produce HTML-escaped output.
+ */
+public class HtmlTextOutput extends AbstractTextOutput {
+ public HtmlTextOutput(PrintWriter out, boolean compact) {
+ super(compact);
+ setPrintWriter(out);
+ }
+
+ @Override
+ public void print(char c) {
+ print(String.valueOf(c));
+ }
+
+ @Override
+ public void print(char[] s) {
+ print(String.valueOf(s));
+ }
+
+ @Override
+ public void print(String s) {
+ super.print(Util.escapeXml(s));
+ }
+
+ @Override
+ public void printOpt(char c) {
+ printOpt(String.valueOf(c));
+ }
+
+ @Override
+ public void printOpt(char[] s) {
+ printOpt(String.valueOf(s));
+ }
+
+ @Override
+ public void printOpt(String s) {
+ super.printOpt(Util.escapeXml(s));
+ }
+
+ /**
+ * Print unescaped data into the output.
+ */
+ public void printRaw(String s) {
+ super.print(s);
+ }
+
+ /**
+ * Print unescaped data into the output.
+ */
+ public void printRawOpt(String s) {
+ super.printOpt(s);
+ }
+}
\ No newline at end of file