/* * Copyright 2014 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.dev.jjs.SourceInfo; import com.google.gwt.thirdparty.debugging.sourcemap.FilePosition; import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGenerator; /** * Writes a sorted stream of mappings to a sourcemap. Automatically merges mappings that have * adjacent or overlapping JavaScript ranges and also point to the same Java line. */ class SourceMappingWriter { private final SourceMapGenerator out; // There may be zero or one mappings in the buffer. // It's represented as separate fields to avoid unnecessary memory allocation. private boolean empty; private String javaFile; private int javaLine; // one-based // the JavaScript range so far (zero-based) private int startLine; private int startColumn; private int endLine; private int endColumn; SourceMappingWriter(SourceMapGenerator out) { this.out = out; this.empty = true; } /** * Sends one mapping to the sourcemap. * * <p>The mappings must be sorted by JavaScript starting position. * * <p>The output is buffered, so the caller must call {@link #flush} when done. */ void addMapping(Range nextRange, String javaName) { SourceInfo nextInfo = nextRange.getSourceInfo(); if (!canMerge(nextRange, nextInfo, javaName)) { flush(null); } if (empty) { // Start a new range. javaFile = nextInfo.getFileName(); javaLine = nextInfo.getStartLine(); startLine = nextRange.getStartLine(); startColumn = nextRange.getStartColumn(); endLine = nextRange.getEndLine(); endColumn = nextRange.getEndColumn(); empty = false; if (javaName != null) { flush(javaName); // Don't merge mappings with Java names. } return; } // Merge with the buffer by adjusting the end of the JavaScript range if needed. // (It's rarely needed because the range of a Java statement usually comes before // any subexpressions within that statement, and there is rarely more than one Java // statement per line.) int nextEndLine = nextRange.getEndLine(); if (nextEndLine < endLine) { return; // The multi-line range in the buffer already covers it. } int nextEndColumn = nextRange.getEndColumn(); if (nextEndLine == endLine && nextEndColumn <= endColumn) { return; // The range in the buffer already covers it. } endLine = nextEndLine; endColumn = nextEndColumn; } /** * Writes any buffered mappings to the source map generator. */ void flush() { flush(null); } /** * Returns true if there is a mapping in the buffer that we can merge with. */ private boolean canMerge(Range nextRange, SourceInfo nextInfo, String javaName) { if (empty) { return false; // Nothing in the buffer. } if (javaName != null) { return false; // Don't merge mappings with Java names. } // The ranges were sorted by starting position. Therefore we only need to to check // that our ending position touches or overlaps their starting position. if (endLine < nextRange.getStartLine()) { return false; // Not adjacent because they're on separate JavaScript lines. } if (endLine == nextRange.getStartLine() && endColumn < nextRange.getStartColumn()) { // Not adjacent due to unmapped characters between JavaScript ranges. // (In theory we could relax this check if there is only whitespace between // JavaScript ranges due to pretty-printing.) return false; } if (javaLine != nextInfo.getStartLine()) { return false; // They don't map to the same Java line. } return javaFile.equals(nextInfo.getFileName()); } /** * Flush the mapping in the buffer and annotate it with the given Java name. */ private void flush(String javaName) { if (empty) { return; } // Starting with V3, SourceMap line numbers are zero-based. // GWT's line numbers for Java files originally came from the JDT, which is 1-based, // so adjust them here to avoid an off-by-one error in debuggers. out.addMapping(javaFile, javaName, new FilePosition(javaLine - 1, 0), new FilePosition(startLine, startColumn), new FilePosition(endLine, endColumn)); empty = true; // don't write it twice. } }