/* * 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.impl.JsSourceMapBuilder; import com.google.gwt.core.ext.linker.impl.NamedRange; import com.google.gwt.core.ext.linker.impl.StatementRangesBuilder; import com.google.gwt.core.ext.soyc.Range; import com.google.gwt.dev.MinimalRebuildCache; import com.google.gwt.dev.StringAnalyzableTypeEnvironment; import com.google.gwt.dev.jjs.JsSourceMap; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.jjs.ast.JTypeOracle; import com.google.gwt.thirdparty.guava.common.collect.Lists; import junit.framework.TestCase; import java.util.List; import java.util.Map; /** * Tests for {@link JsTypeLinker}. */ public class JsTypeLinkerTest extends TestCase { private int lines; public void testLink() { NamedRange programRange = new NamedRange("Program"); NamedRange someModelARange = new NamedRange("com.some.app.SomeAModel"); NamedRange someModelBRange = new NamedRange("com.some.app.SomeBModel"); NamedRange someControllerRange = new NamedRange("com.some.app.SomeController"); NamedRange entryPointRange = new NamedRange("com.some.app.EntryPoint"); List<NamedRange> classRanges = Lists.newArrayList(someModelARange, someModelBRange, someControllerRange, entryPointRange); StatementRangesBuilder srb = new StatementRangesBuilder(); JsSourceMapBuilder smb = new JsSourceMapBuilder(); // Build the original JS and log boundaries. StringBuilder sb = new StringBuilder(); appendStatement(sb, srb, smb, "<preamble>\n"); appendStatement(sb, srb, smb, "<java.lang.Object />\n"); appendStatement(sb, srb, smb, "<java.lang.Class />\n"); appendStatement(sb, srb, smb, "</preamble>\n"); { programRange.setStartPosition(sb.length()); programRange.setStartLineNumber(lines); appendTypeStatement(sb, srb, smb, someModelARange, "<com.some.app.SomeModelA>\n"); appendTypeStatement(sb, srb, smb, someModelBRange, "<com.some.app.SomeModelB>\n"); appendTypeStatement(sb, srb, smb, someControllerRange, "<com.some.app.SomeController>\n"); appendTypeStatement(sb, srb, smb, entryPointRange, "<com.some.app.EntryPoint>\n"); programRange.setEndPosition(sb.length()); programRange.setEndLineNumber(lines); } appendStatement(sb, srb, smb, "<epilogue>\n"); appendStatement(sb, srb, smb, "<Some Bootstrap Code>\n"); appendStatement(sb, srb, smb, "</epilogue>\n"); String originalJs = sb.toString(); MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache(); // Create type inheritance. Map<String, String> superClassesByClass = minimalRebuildCache.getImmediateTypeRelations().getImmediateSuperclassesByClass(); StringAnalyzableTypeEnvironment typeEnvironment = minimalRebuildCache.getTypeEnvironment(); typeEnvironment.recordTypeEnclosesMethod("java.lang.Object", "java.lang.Object::$clinit()V"); superClassesByClass.put("java.lang.Class", "java.lang.Object"); typeEnvironment.recordTypeEnclosesMethod("java.lang.Class", "java.lang.Class::$clinit()V"); superClassesByClass.put("com.some.app.SomeAModel", "java.lang.Object"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeAModel", "com.some.app.SomeAModel::$clinit()V"); superClassesByClass.put("com.some.app.SomeBModel", "java.lang.Object"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeBModel", "com.some.app.SomeBModel::$clinit()V"); superClassesByClass.put("com.some.app.SomeController", "java.lang.Object"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController", "com.some.app.SomeController::$clinit()V"); superClassesByClass.put("com.some.app.EntryPoint", "java.lang.Object"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.EntryPoint", "com.some.app.EntryPoint::$clinit()V"); // Record root types. minimalRebuildCache.setRootTypeNames(Lists.newArrayList("com.some.app.EntryPoint")); minimalRebuildCache.setEntryMethodNames( Lists.newArrayList("com.some.app.EntryPoint::onModuleLoad()V")); typeEnvironment.recordTypeEnclosesMethod("com.some.app.EntryPoint", "com.some.app.EntryPoint::onModuleLoad()V"); // Record type references. minimalRebuildCache.addTypeReference("com.some.app.EntryPoint", "com.some.app.SomeController"); typeEnvironment.recordMethodInstantiatesType("com.some.app.EntryPoint::onModuleLoad()V", "com.some.app.SomeController"); typeEnvironment.recordMethodCallsMethod("com.some.app.EntryPoint::onModuleLoad()V", "com.some.app.SomeController::createData()V"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController", "com.some.app.SomeController::createData()V"); minimalRebuildCache.addTypeReference("com.some.app.SomeController", "com.some.app.SomeBModel"); typeEnvironment.recordMethodInstantiatesType("com.some.app.SomeController::createData()V", "com.some.app.SomeBModel"); typeEnvironment.recordMethodCallsMethod("com.some.app.SomeController::createData()V", "com.some.app.SomeBModel::SomeBModel()V"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeBModel", "com.some.app.SomeBModel::SomeBModel()V"); minimalRebuildCache.addTypeReference("com.some.app.SomeController", "com.some.app.SomeAModel"); typeEnvironment.recordMethodInstantiatesType("com.some.app.SomeController::createData()V", "com.some.app.SomeAModel"); typeEnvironment.recordMethodCallsMethod("com.some.app.SomeController::createData()V", "com.some.app.SomeAModel::SomeAModel()V"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeAModel", "com.some.app.SomeAModel::SomeAModel()V"); JsTypeLinker jsTypeLinker = new JsTypeLinker(TreeLogger.NULL, new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange, minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache)); // Run the JS Type Linker. jsTypeLinker.exec(); // Verify that the linker output all the expected classes and sorted them alphabetically. assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n" + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelA>\n" + "<com.some.app.SomeModelB>\n" + "<com.some.app.SomeController>\n" + "<epilogue>\n<Some Bootstrap Code>\n</epilogue>\n", jsTypeLinker.getJs()); assertEquals(Lists.newArrayList("preamble", "java.lang.Object", "java.lang.Class", "/preamble", "com.some.app.EntryPoint", "com.some.app.SomeModelA", "com.some.app.SomeModelB", "com.some.app.SomeController", "epilogue", "Some Bootstrap Code", "/epilogue"), getTypeNames(jsTypeLinker.getSourceInfoMap())); assertEquals(11, jsTypeLinker.getSourceInfoMap().getLines()); // Make SomeModelB the super class of SomeModelA and then verify that B comes out before A. superClassesByClass.put("com.some.app.SomeAModel", "com.some.app.SomeBModel"); jsTypeLinker = new JsTypeLinker(TreeLogger.NULL, new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange, minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache)); jsTypeLinker.exec(); assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n" + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelB>\n" + "<com.some.app.SomeModelA>\n" + "<com.some.app.SomeController>\n" + "<epilogue>\n<Some Bootstrap Code>\n</epilogue>\n", jsTypeLinker.getJs()); assertEquals(Lists.newArrayList("preamble", "java.lang.Object", "java.lang.Class", "/preamble", "com.some.app.EntryPoint", "com.some.app.SomeModelB", "com.some.app.SomeModelA", "com.some.app.SomeController", "epilogue", "Some Bootstrap Code", "/epilogue"), getTypeNames(jsTypeLinker.getSourceInfoMap())); assertEquals(11, jsTypeLinker.getSourceInfoMap().getLines()); // Stop referring to SomeModelA from the Controller and verify that SomeModelA is not in the // output. minimalRebuildCache.removeReferencesFrom("com.some.app.SomeController"); minimalRebuildCache.addTypeReference("com.some.app.SomeController", "com.some.app.SomeBModel"); typeEnvironment.removeControlFlowIndexesFor("com.some.app.SomeController"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController", "com.some.app.SomeController::createData()V"); typeEnvironment.recordTypeEnclosesMethod("com.some.app.SomeController", "com.some.app.SomeController::$clinit()V"); typeEnvironment.recordMethodInstantiatesType("com.some.app.SomeController::createData()V", "com.some.app.SomeBModel"); typeEnvironment.recordMethodCallsMethod("com.some.app.SomeController::createData()V", "com.some.app.SomeBModel::SomeBModel()V"); jsTypeLinker = new JsTypeLinker(TreeLogger.NULL, new JsNoopTransformer(originalJs, srb.build(), smb.build()), classRanges, programRange, minimalRebuildCache, new JTypeOracle(null, minimalRebuildCache)); jsTypeLinker.exec(); assertEquals("<preamble>\n<java.lang.Object />\n<java.lang.Class />\n</preamble>\n" + "<com.some.app.EntryPoint>\n" + "<com.some.app.SomeModelB>\n" + "<com.some.app.SomeController>\n" + "<epilogue>\n<Some Bootstrap Code>\n</epilogue>\n", jsTypeLinker.getJs()); assertEquals(Lists.newArrayList("preamble", "java.lang.Object", "java.lang.Class", "/preamble", "com.some.app.EntryPoint", "com.some.app.SomeModelB", "com.some.app.SomeController", "epilogue", "Some Bootstrap Code", "/epilogue"), getTypeNames(jsTypeLinker.getSourceInfoMap())); assertEquals(10, jsTypeLinker.getSourceInfoMap().getLines()); } private void appendStatement(StringBuilder sb, StatementRangesBuilder statementRangesBuilder, JsSourceMapBuilder jsSourceMapBuilder, String statement) { String typeName = statement.replace(" />", "").replace("<", "").replace(">", "").replace("\n", ""); statementRangesBuilder.addStartPosition(sb.length()); List<Range> ranges = Lists.newArrayList(new Range(0, statement.length(), lines, 0, lines + 1, statement.length(), SourceOrigin.create(0, statement.length(), 0, typeName))); jsSourceMapBuilder.append(new JsSourceMap(ranges, statement.length(), 1)); sb.append(statement); statementRangesBuilder.addEndPosition(sb.length()); lines++; } private void appendTypeStatement(StringBuilder sb, StatementRangesBuilder statementRangesBuilder, JsSourceMapBuilder jsSourceMapBuilder, NamedRange someModelARange, String statement) { someModelARange.setStartPosition(sb.length()); someModelARange.setStartLineNumber(lines); appendStatement(sb, statementRangesBuilder, jsSourceMapBuilder, statement); someModelARange.setEndPosition(sb.length()); someModelARange.setEndLineNumber(lines); } private List<String> getTypeNames(JsSourceMap sourceInfoMap) { List<String> typeNames = Lists.newArrayList(); for (Range range : sourceInfoMap.getRanges()) { typeNames.add(range.getSourceInfo().getFileName()); } return typeNames; } }