blob: 62cbc3c53cd08b9c89763284d48410ffdc646ed8 [file] [log] [blame]
/*
* Copyright 2011 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.soyc;
import com.google.gwt.core.ext.linker.SyntheticArtifact;
import com.google.gwt.core.linker.SymbolMapsLinker;
import com.google.gwt.dev.jjs.Correlation;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JsSourceMap;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceInfoCorrelation;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorV3;
import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapParseException;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Collections;
import java.util.List;
/**
* Creates Closure Compatible SourceMaps.
*/
public class SourceMapRecorder {
/**
* Generates a sourcemap for each fragment in the list.
*
* @param sourceFilePrefix the prefix that a debugger should add to the beginning of each
* filename in a sourcemap to determine the file's full URL.
* If null, filenames are relative to the sourcemap's URL.
*/
public static List<SyntheticArtifact> exec(int permutationId,
List<JsSourceMap> fragmentMaps, String sourceFilePrefix) {
try {
return new SourceMapRecorder(permutationId, fragmentMaps, sourceFilePrefix).createArtifacts();
} catch (Exception e) {
throw new InternalCompilerException(e.toString(), e);
}
}
/**
* Generates a sourcemap for each fragment in the list, with JavaScript-to-Java
* name mappings included.
*/
public static List<SyntheticArtifact> execWithJavaNames(int permutationId,
List<JsSourceMap> fragmentMaps, String sourceFilePrefix) {
try {
SourceMapRecorder recorder = new SourceMapRecorder(permutationId, fragmentMaps,
sourceFilePrefix);
recorder.wantJavaNames = true;
return recorder.createArtifacts();
} catch (Exception e) {
throw new InternalCompilerException(e.toString(), e);
}
}
private final int permutationId;
private final List<JsSourceMap> fragmentMaps;
private final String sourceRoot;
private boolean wantJavaNames;
private SourceMapRecorder(int permutationId, List<JsSourceMap> fragmentMaps, String sourceRoot) {
this.permutationId = permutationId;
this.fragmentMaps = fragmentMaps;
this.sourceRoot = sourceRoot;
}
private List<SyntheticArtifact> createArtifacts()
throws IOException, SourceMapParseException {
Event event = SpeedTracerLogger.start(CompilerEventType.SOURCE_MAP_RECORDER);
List<SyntheticArtifact> toReturn = Lists.newArrayList();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SourceMapGeneratorV3 generator = new SourceMapGeneratorV3();
int fragment = 0;
for (JsSourceMap sourceMap : fragmentMaps) {
generator.reset();
if (sourceRoot != null) {
generator.setSourceRoot(sourceRoot);
}
addExtensions(generator, fragment);
addMappings(new SourceMappingWriter(generator), sourceMap);
baos.reset();
OutputStreamWriter out = new OutputStreamWriter(baos);
generator.appendTo(out, "sourceMap" + fragment);
out.flush();
toReturn.add(new SymbolMapsLinker.SourceMapArtifact(permutationId, fragment,
baos.toByteArray(), sourceRoot));
fragment++;
}
event.end();
return toReturn;
}
private void addExtensions(SourceMapGeneratorV3 generator, int fragment)
throws SourceMapParseException {
// We don't convert to a string here so that the values will be added
// to the JSON as a number instead of a string.
generator.addExtension("x_gwt_permutation", permutationId);
generator.addExtension("x_gwt_fragment", fragment);
}
/**
* Adds the source mappings for one JavaScript file to its sourcemap.
* Consolidates adjacent or overlapping ranges to reduce the amount of data that the JavaScript
* debugger has to load.
*/
private void addMappings(SourceMappingWriter output, JsSourceMap mappings) {
List<Range> ranges = Lists.newArrayList(mappings.getRanges());
Collections.sort(ranges, Range.DEPENDENCY_ORDER_COMPARATOR);
for (Range range : ranges) {
SourceInfo info = range.getSourceInfo();
output.addMapping(range, getJavaName(info));
}
output.flush();
}
/**
* Returns the name to be added to the "names" field in the sourcemap.
*
* <p>The name is currently always a Java identifier, but in theory may be any Java expression.
* For example, a compiler-introduced temporary variable could be annotated with the expression
* that produced it.
*
* <p>The name should only be set if the JavaScript range covers one JavaScript identifier.
* (Otherwise return null.)
*/
private String getJavaName(SourceInfo sourceInfo) {
if (!wantJavaNames) {
return null;
}
if (!(sourceInfo instanceof SourceInfoCorrelation)) {
return null;
}
Correlation correlation = ((SourceInfoCorrelation) sourceInfo).getPrimaryCorrelation();
if (correlation == null) {
return null;
}
// Conserve space by not recording the package name. The sourcemap already contains the full
// path of the Java file (in the "sources" field), which is usually enough to identify
// the package. (The name may be a synthetic method name.)
return correlation.getIdent();
}
}