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