/* * 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.dev.jjs.impl; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.linker.StatementRanges; import com.google.gwt.core.ext.linker.impl.JsSourceMapBuilder; import com.google.gwt.core.ext.linker.impl.JsSourceMapExtractor; import com.google.gwt.core.ext.linker.impl.NamedRange; import com.google.gwt.core.ext.linker.impl.StatementRangesBuilder; import com.google.gwt.core.ext.linker.impl.StatementRangesExtractor; import com.google.gwt.dev.MinimalRebuildCache; import com.google.gwt.dev.jjs.JsSourceMap; import com.google.gwt.dev.jjs.ast.JTypeOracle; import com.google.gwt.thirdparty.guava.common.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Sets; import java.util.Collections; import java.util.List; import java.util.Set; /** * Transforms program JS source by performing a per-type link. * <p> * Provided JS and Ranges are used to grab new JS type chunks. A RebuildCache is used to cache per * type JS, statement ranges and sourcemaps (possibly across compiles) and calculate the set of * reachable types. JTypeOracle is used to order linked output. */ public class JsTypeLinker extends JsAbstractTextTransformer { private static final String FOOTER_NAME = "-footer-"; private static final String HEADER_NAME = "-header-"; private final NamedRange footerRange; private final NamedRange headerRange; private final StringBuilder jsBuilder; private final JsSourceMapBuilder jsSourceMapBuilder = new JsSourceMapBuilder(); private final JsSourceMapExtractor jsSourceMapExtractor; private final Set<String> linkedTypeNames = Sets.newHashSet(); private TreeLogger logger; private final MinimalRebuildCache minimalRebuildCache; private final StatementRangesBuilder statementRangesBuilder = new StatementRangesBuilder(); private final StatementRangesExtractor statementRangesExtractor; private final JTypeOracle typeOracle; private final List<NamedRange> typeRanges; public JsTypeLinker(TreeLogger logger, JsAbstractTextTransformer textTransformer, List<NamedRange> typeRanges, NamedRange programTypeRange, MinimalRebuildCache minimalRebuildCache, JTypeOracle typeOracle) { super(textTransformer); this.logger = logger; this.statementRangesExtractor = new StatementRangesExtractor(statementRanges); this.jsSourceMapExtractor = sourceInfoMap.createExtractor(); this.typeRanges = typeRanges; this.headerRange = new NamedRange(HEADER_NAME, 0, programTypeRange.getStartPosition(), 0, programTypeRange.getStartLineNumber()); this.footerRange = new NamedRange(FOOTER_NAME, programTypeRange.getEndPosition(), js.length(), programTypeRange.getEndLineNumber(), sourceInfoMap.getLines()); this.minimalRebuildCache = minimalRebuildCache; this.typeOracle = typeOracle; // Presize the jsBuilder to avoid content copying during expansion. this.jsBuilder = new StringBuilder(minimalRebuildCache.knowsLastLinkedJsBytes() ? (int) ( minimalRebuildCache.getLastLinkedJsBytes() * 1.05) : sourceInfoMap.getBytes()); } @Override public void exec() { logger = logger.branch(TreeLogger.INFO, "Linking per-type JS with " + typeRanges.size() + " new/changed types."); linkAll(computeReachableTypes()); } @Override protected void updateSourceInfoMap() { // Already updated in exec(); } private List<String> computeReachableTypes() { List<String> reachableTypeNames = Lists.newArrayList(minimalRebuildCache.computeReachableTypeNames()); Collections.sort(reachableTypeNames); return reachableTypeNames; } private void extractOne(NamedRange typeRange) { String typeName = typeRange.getName(); minimalRebuildCache.setJsForType(logger, typeName, js.substring(typeRange.getStartPosition(), typeRange.getEndPosition())); minimalRebuildCache.setStatementRangesForType(typeName, statementRangesExtractor.extract(typeRange.getStartPosition(), typeRange.getEndPosition())); minimalRebuildCache.setSourceMapForType(typeName, jsSourceMapExtractor.extract( typeRange.getStartPosition(), typeRange.getEndPosition(), typeRange.getStartLineNumber(), typeRange.getEndLineNumber())); } private void linkAll(List<String> reachableTypeNames) { // Extract new JS. if (minimalRebuildCache.getJs(HEADER_NAME) == null) { extractOne(headerRange); } for (NamedRange typeRange : typeRanges) { extractOne(typeRange); } extractOne(footerRange); // Link new and old JS. linkOne(HEADER_NAME); for (String reachableTypeName : reachableTypeNames) { linkOne(reachableTypeName); } linkOne(FOOTER_NAME); logger.log(TreeLogger.TRACE, "prelink JS size = " + js.length()); logger.log(TreeLogger.TRACE, "prelink sourcemap = " + sourceInfoMap.getBytes() + " bytes and " + sourceInfoMap.getLines() + " lines"); js = jsBuilder.toString(); statementRanges = statementRangesBuilder.build(); sourceInfoMap = jsSourceMapBuilder.build(); minimalRebuildCache.setLastLinkedJsBytes(js.length()); logger.log(TreeLogger.TRACE, "postlink JS size = " + js.length()); logger.log(TreeLogger.TRACE, "postlink sourcemap = " + sourceInfoMap.getBytes() + " bytes and " + sourceInfoMap.getLines() + " lines"); } private void linkOne(String typeName) { if (linkedTypeNames.contains(typeName)) { return; } linkedTypeNames.add(typeName); String typeJs = minimalRebuildCache.getJs(typeName); if (typeJs == null) { return; } // Link super types before sub types. String superTypeName = typeOracle.getSuperTypeName(typeName); if (superTypeName != null) { linkOne(superTypeName); } logger.log(TreeLogger.SPAM, "linking type " + typeName + " (" + typeJs.length() + " bytes)"); StatementRanges typeStatementRanges = minimalRebuildCache.getStatementRanges(typeName); JsSourceMap typeSourceMap = minimalRebuildCache.getSourceMap(typeName); jsBuilder.append(typeJs); statementRangesBuilder.append(typeStatementRanges); jsSourceMapBuilder.append(typeSourceMap); } }