| /* |
| * 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(); |
| } |
| } |