| /* |
| * 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; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.linker.ArtifactSet; |
| import com.google.gwt.core.ext.linker.StatementRanges; |
| import com.google.gwt.dev.cfg.ModuleDef; |
| import com.google.gwt.dev.javac.CompilationUnit; |
| import com.google.gwt.dev.javac.CompiledClass; |
| import com.google.gwt.dev.javac.GeneratedUnit; |
| import com.google.gwt.dev.javac.Shared; |
| import com.google.gwt.dev.jjs.JsSourceMap; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JTypeOracle; |
| import com.google.gwt.dev.jjs.ast.JTypeOracle.ImmediateTypeRelations; |
| import com.google.gwt.dev.jjs.impl.RapidTypeAnalyzer; |
| import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.IntTypeMapper; |
| import com.google.gwt.dev.js.JsIncrementalNamer.JsIncrementalNamerState; |
| import com.google.gwt.dev.resource.Resource; |
| import com.google.gwt.dev.util.Name.InternalName; |
| import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting; |
| import com.google.gwt.thirdparty.guava.common.base.Objects; |
| import com.google.gwt.thirdparty.guava.common.base.Predicates; |
| import com.google.gwt.thirdparty.guava.common.collect.HashMultimap; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; |
| import com.google.gwt.thirdparty.guava.common.collect.Iterables; |
| import com.google.gwt.thirdparty.guava.common.collect.Maps; |
| import com.google.gwt.thirdparty.guava.common.collect.Multimap; |
| import com.google.gwt.thirdparty.guava.common.collect.Multimaps; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import cern.colt.list.IntArrayList; |
| |
| import java.io.Serializable; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * MinimalRebuildCache contains compiler information that can be persisted between compiles to |
| * decrease compilation time. |
| * <p> |
| * Cached information is specific to a single permutation and it is the responsibility of the |
| * framework driving the Compiler to supply the right Cache instance for the right Module |
| * configuration and to make sure that the Compiler is only processing one permutation at a time. |
| * <p> |
| * All type names referenced here are assumed to be binary names. |
| * <p> |
| * A "typeName" here might be a root type or nested type but a "compilationUnitName" here will |
| * always be the name of just the root type in a compilation unit. |
| */ |
| public class MinimalRebuildCache implements Serializable { |
| |
| private static void appendSubTypes(Set<String> accumulatedTypeNames, Set<String> parentTypeNames, |
| JTypeOracle typeOracle) { |
| for (String parentTypeName : parentTypeNames) { |
| Iterables.addAll(accumulatedTypeNames, typeOracle.getSubTypeNames(parentTypeName)); |
| } |
| } |
| |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| private static void copyCollection(Collection fromCollection, Collection toCollection) { |
| toCollection.clear(); |
| toCollection.addAll(fromCollection); |
| } |
| |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| private static void copyMap(Map fromMap, Map toMap) { |
| toMap.clear(); |
| toMap.putAll(fromMap); |
| } |
| |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| private static void copyMultimap(Multimap fromMap, Multimap toMap) { |
| toMap.clear(); |
| toMap.putAll(fromMap); |
| } |
| |
| /** |
| * Diffs lastModifiedByResourcePath from the previous compile against currentResources from the |
| * current compile. modifiedResourcePaths is wiped and recreated to be a list of just the modified |
| * or deleted resources, deletedResourcePaths is wiped and recreated to be a list of just the |
| * deleted resources and modifiedResourcePaths is updated in place with new lastModified dates. |
| */ |
| private static void recordModifiedResources(Map<String, Long> currentModifiedByResourcePath, |
| Map<String, Long> lastModifiedByResourcePath, Set<String> modifiedResourcePaths, |
| Set<String> deletedResourcePaths) { |
| deletedResourcePaths.clear(); |
| modifiedResourcePaths.clear(); |
| |
| Set<String> currentResourcePaths = Sets.newHashSet(); |
| for (Entry<String, Long> entry : currentModifiedByResourcePath.entrySet()) { |
| String currentResourcePath = entry.getKey(); |
| Long currentResourceModified = entry.getValue(); |
| |
| currentResourcePaths.add(currentResourcePath); |
| Long lastKnownModified = |
| lastModifiedByResourcePath.put(currentResourcePath, currentResourceModified); |
| if (!Objects.equal(lastKnownModified, currentResourceModified)) { |
| // Added or Modified resource. |
| modifiedResourcePaths.add(currentResourcePath); |
| } |
| } |
| |
| // Removed resources. |
| { |
| // Figure out which resources were removed. |
| Set<String> removedResourcePaths = Sets.newHashSet( |
| Sets.difference(lastModifiedByResourcePath.keySet(), currentResourcePaths)); |
| // Log them as "modified". |
| deletedResourcePaths.addAll(removedResourcePaths); |
| modifiedResourcePaths.addAll(removedResourcePaths); |
| // Remove any path to modified date entries for them. |
| for (String removedResourcePath : removedResourcePaths) { |
| lastModifiedByResourcePath.remove(removedResourcePath); |
| } |
| } |
| } |
| |
| private static Set<String> resourcePathsToCompilationUnitNames(Set<String> resourcePaths) { |
| Set<String> compilationUnitNames = Sets.newHashSet(); |
| for (String resourcePath : resourcePaths) { |
| compilationUnitNames.add(Shared.toTypeName(resourcePath)); |
| } |
| return compilationUnitNames; |
| } |
| |
| private static Map<String, Long> resourcesToModifiedByPath(Collection<Resource> resources) { |
| Map<String, Long> modifiedByPath = Maps.newHashMap(); |
| for (Resource resource : resources) { |
| modifiedByPath.put(resource.getPath(), resource.getLastModified()); |
| } |
| return modifiedByPath; |
| } |
| |
| /* |
| * Update copyFrom() whenever adding more fields. |
| */ |
| protected final ImmediateTypeRelations immediateTypeRelations = new ImmediateTypeRelations(); |
| private final Map<String, String> compilationUnitTypeNameByNestedTypeName = Maps.newHashMap(); |
| private final Map<String, String> contentHashByGeneratedTypeName = Maps.newHashMap(); |
| private final Set<String> deletedCompilationUnitNames = Sets.newHashSet(); |
| private final Set<String> deletedDiskSourcePaths = Sets.newHashSet(); |
| private final Set<String> deletedResourcePaths = Sets.newHashSet(); |
| private final Set<String> dualJsoImplInterfaceNames = Sets.newHashSet(); |
| private final Map<String, String> descriptionByexportedGlobalNames = Maps.newHashMap(); |
| private final Multimap<String, String> exportedGlobalNamesByTypeName = HashMultimap.create(); |
| private final ArtifactSet generatedArtifacts = new ArtifactSet(); |
| private final Multimap<String, String> generatedCompilationUnitNamesByReboundTypeNames = |
| HashMultimap.create(); |
| private final IntTypeMapper intTypeMapper = new IntTypeMapper(); |
| private final Map<String, String> jsByTypeName = Maps.newHashMap(); |
| private final JsIncrementalNamerState jsIncrementalNamerState = new JsIncrementalNamerState(); |
| private final Set<String> jsoStatusChangedTypeNames = Sets.newHashSet(); |
| private final Set<String> jsoTypeNames = Sets.newHashSet(); |
| private Integer lastLinkedJsBytes; |
| private final Map<String, Long> lastModifiedByDiskSourcePath = Maps.newHashMap(); |
| private final Map<String, Long> lastModifiedByResourcePath = Maps.newHashMap(); |
| private final Set<String> lastReachableTypeNames = Sets.newHashSet(); |
| private final Set<String> modifiedCompilationUnitNames = Sets.newHashSet(); |
| private final Set<String> modifiedDiskSourcePaths = Sets.newHashSet(); |
| private final Set<String> modifiedResourcePaths = Sets.newHashSet(); |
| private final Multimap<String, String> nestedTypeNamesByUnitTypeName = HashMultimap.create(); |
| private final Set<String> preambleTypeNames = Sets.newHashSet(); |
| private transient ImmutableSet<String> processedStaleTypeNames = ImmutableSet.<String> of(); |
| private final Multimap<String, String> rebinderTypeNamesByReboundTypeName = HashMultimap.create(); |
| private final Multimap<String, String> reboundTypeNamesByGeneratedCompilationUnitNames = |
| HashMultimap.create(); |
| private final Multimap<String, String> reboundTypeNamesByInputResource = HashMultimap.create(); |
| private final Multimap<String, String> referencedTypeNamesByTypeName = HashMultimap.create(); |
| private final Set<String> rootTypeNames = Sets.newHashSet(); |
| private final Set<String> singleJsoImplInterfaceNames = Sets.newHashSet(); |
| private final Set<String> sourceCompilationUnitNames = Sets.newHashSet(); |
| private final Map<String, JsSourceMap> sourceMapsByTypeName = Maps.newHashMap(); |
| private final Set<String> staleTypeNames = Sets.newHashSet(); |
| private final Map<String, StatementRanges> statementRangesByTypeName = Maps.newHashMap(); |
| private StringAnalyzableTypeEnvironment typeEnvironment = new StringAnalyzableTypeEnvironment(); |
| private final Multimap<String, String> typeNamesByReferencingTypeName = HashMultimap.create(); |
| |
| public String addExportedGlobalName( |
| String exportedGlobalName, String description, String inTypeName) { |
| exportedGlobalNamesByTypeName.put(inTypeName, exportedGlobalName); |
| return descriptionByexportedGlobalNames.put(exportedGlobalName, description); |
| } |
| |
| /** |
| * Accumulates generated artifacts so that they can be output on recompiles even if no generators |
| * are run. |
| */ |
| public void addGeneratedArtifacts(ArtifactSet generatedArtifacts) { |
| this.generatedArtifacts.addAll(generatedArtifacts); |
| } |
| |
| public void addModifiedCompilationUnitNames(TreeLogger logger, |
| Set<String> modifiedCompilationUnitNames) { |
| logger.log(TreeLogger.DEBUG, "adding to cached list of known modified compilation units " |
| + modifiedCompilationUnitNames); |
| this.modifiedCompilationUnitNames.addAll(modifiedCompilationUnitNames); |
| } |
| |
| public void addSourceCompilationUnitName(String sourceCompilationUnitName) { |
| // sourceCompilationUnitNames contains all compilation unit type names seen so far even |
| // across recompiles. There is a need to know whether a unit is available in source form ( |
| // either compiled from source in this iteration or its AST available from cache) so that |
| // we insert the correct type of reference when we see a BinaryTypeReference in the JDT |
| // AST. {@see GwtAstBuilder}. |
| // NOTE: DO NOT RESET OR CLEAR THIS SET. |
| this.sourceCompilationUnitNames.add(sourceCompilationUnitName); |
| } |
| |
| public void addTypeReference(String fromTypeName, String toTypeName) { |
| referencedTypeNamesByTypeName.put(fromTypeName, toTypeName); |
| typeNamesByReferencingTypeName.put(toTypeName, fromTypeName); |
| } |
| |
| public void associateReboundTypeWithGeneratedCompilationUnitName(String reboundTypeName, |
| String generatedCompilationUnitName) { |
| reboundTypeNamesByGeneratedCompilationUnitNames.put(generatedCompilationUnitName, |
| reboundTypeName); |
| } |
| |
| /** |
| * Record that a Generator that was ran as a result of a GWT.create(ReboundType.class) call read a |
| * particular resource. |
| */ |
| public void associateReboundTypeWithInputResource(String reboundTypeName, |
| String inputResourcePath) { |
| reboundTypeNamesByInputResource.put(inputResourcePath, reboundTypeName); |
| } |
| |
| public void clearPerTypeJsCache() { |
| rootTypeNames.clear(); |
| preambleTypeNames.clear(); |
| |
| deletedResourcePaths.clear(); |
| modifiedResourcePaths.clear(); |
| |
| deletedDiskSourcePaths.clear(); |
| modifiedDiskSourcePaths.clear(); |
| contentHashByGeneratedTypeName.clear(); |
| |
| jsByTypeName.clear(); |
| referencedTypeNamesByTypeName.clear(); |
| sourceMapsByTypeName.clear(); |
| statementRangesByTypeName.clear(); |
| typeNamesByReferencingTypeName.clear(); |
| } |
| |
| public void clearRebinderTypeAssociations(String rebinderTypeName) { |
| rebinderTypeNamesByReboundTypeName.values().remove(rebinderTypeName); |
| } |
| |
| public void clearReboundTypeAssociations(String reboundTypeName) { |
| reboundTypeNamesByInputResource.values().remove(reboundTypeName); |
| reboundTypeNamesByGeneratedCompilationUnitNames.values().remove(reboundTypeName); |
| } |
| |
| /** |
| * Calculates the set of stale types and clears their cached Js, StatementRanges and SourceMaps. |
| * <p> |
| * The calculation of stale types starts with the list of known modified types and expands that |
| * set using various patterns intended to find any types whose output JS would be affected by the |
| * changes in the modified types. For example if the parent of class Foo was changed then the |
| * castmaps in all children of Foo need to be recreated. |
| * <p> |
| * In some ways this process is similar to that performed by the CompilationUnitInvalidator but it |
| * differs both in what type of cached objects are being cleared (JS versus CompilationUnits) and |
| * in what invalidation rules must be applied. CompilationUnitInvalidator is concerned only with |
| * changes in interface while the invalidation here must also look at JSO promotion/demotion and |
| * cast references because of the peculiarities of output JS format. |
| */ |
| public Set<String> computeAndClearStaleTypesCache(TreeLogger logger, JTypeOracle typeOracle) { |
| if (!isPopulated()) { |
| return Sets.newHashSet(); |
| } |
| |
| // Cache the ReboundTypeNames -> GeneratedCompilationUnitNames reverse map as it will be needed |
| // several times. |
| generatedCompilationUnitNamesByReboundTypeNames.clear(); |
| Multimaps.invertFrom(reboundTypeNamesByGeneratedCompilationUnitNames, |
| generatedCompilationUnitNamesByReboundTypeNames); |
| |
| // Store stale type names in a persisted field so that tests can inspect behavior. |
| staleTypeNames.clear(); |
| |
| Set<String> modifiedTypeNames = computeModifiedTypeNames(); |
| |
| // Accumulate the names of stale types resulting from some known type or resource modifications, |
| // using various patterns (sub types, referencing types, etc). |
| { |
| staleTypeNames.addAll(modifiedTypeNames); |
| appendSubTypes(staleTypeNames, modifiedTypeNames, typeOracle); |
| // Marks stale any type that references either a modified type or any subtype of a modified |
| // type. The references of subtypes can be done away with once we revamp Array castmap |
| // generation. |
| ImmutableList<String> modifiedTypeAndSubTypeNames = ImmutableList.copyOf(staleTypeNames); |
| appendReferencingTypes(staleTypeNames, modifiedTypeAndSubTypeNames); |
| appendReferencingTypes(staleTypeNames, jsoStatusChangedTypeNames); |
| staleTypeNames.addAll( |
| computeTypesThatRebindTypes(computeReboundTypesAffectedByModifiedResources())); |
| appendTypesToRegenerateStaleGeneratedTypes(staleTypeNames); |
| |
| // Generator output is affected by types queried from the TypeOracle but changes in these |
| // types are not being directly supported at this time since some of them are already handled |
| // because they are referenced by the Generator output and since changes in subtype queries |
| // probably make GWTRPC output incompatible with a server anyway (and thus already forces a |
| // restart). |
| |
| staleTypeNames.removeAll(JProgram.SYNTHETIC_TYPE_NAMES); |
| } |
| |
| /* |
| * Filter for just those stale types that are actually reachable. Since if they're not reachable |
| * we don't want to artificially traverse them and unnecessarily reveal dependency problems. And |
| * if they have become reachable, since they're missing JS, they will already be fully traversed |
| * when seen in Unify. |
| */ |
| copyCollection(filterUnreachableTypeNames(staleTypeNames), staleTypeNames); |
| |
| // These log lines can be expensive. |
| if (logger.isLoggable(TreeLogger.DEBUG)) { |
| logger.log(TreeLogger.DEBUG, "known modified types = " + modifiedTypeNames); |
| logger.log(TreeLogger.DEBUG, "known modified resources = " + modifiedResourcePaths); |
| logger.log(TreeLogger.DEBUG, |
| "clearing cached output for resulting stale types = " + staleTypeNames); |
| } |
| |
| for (String staleTypeName : staleTypeNames) { |
| clearCachedTypeOutput(staleTypeName); |
| } |
| |
| return Sets.newHashSet(staleTypeNames); |
| } |
| |
| /** |
| * Computes and returns the set of names of deleted types. |
| * <p> |
| * Deleted types are all those types that are nested within compilation units that are known to |
| * have been deleted. |
| */ |
| public Set<String> computeDeletedTypeNames() { |
| return computeNestedTypeNames(deletedCompilationUnitNames); |
| } |
| |
| /** |
| * Computes and returns the set of names of modified types. |
| * <p> |
| * Modified types are all those types that are nested within compilation units that are known to |
| * have been modified. |
| */ |
| public Set<String> computeModifiedTypeNames() { |
| // Accumulate the names of types that are nested within known modified compilation units. |
| return computeNestedTypeNames(modifiedCompilationUnitNames); |
| } |
| |
| /** |
| * Computes and returns the names of the set of types that are referenceable within the method |
| * level control flow starting from the entry methods, immortal codegen types and exported |
| * JsInterop methods. |
| * <p> |
| * Should only be called once per compile, so that the "lastReachableTypeNames" list accurately |
| * reflects the reachable types of the immediately previous compile. |
| */ |
| public Set<String> computeReachableTypeNames() { |
| RapidTypeAnalyzer rapidTypeAnalyzer = new RapidTypeAnalyzer(typeEnvironment); |
| |
| // Artificially reach and traverse immortal codegen types since references to these may have |
| // been synthesized in JS generation. These will not be pruned. |
| for (String immortalCodegenTypeName : JProgram.IMMORTAL_CODEGEN_TYPES_SET) { |
| int immortalCodegenTypeId = typeEnvironment.getTypeIdByName(immortalCodegenTypeName); |
| rapidTypeAnalyzer.markTypeIdReachable(immortalCodegenTypeId); |
| // Includes the clinit method. |
| rapidTypeAnalyzer.markMemberMethodIdsReachable(immortalCodegenTypeId); |
| } |
| |
| // Artificially reach @JsExport and @JsType methods. Since the enclosing types are |
| // not being marked instantiated or reachable there's still a chance that they will be pruned |
| // if no instantiations or static references are found. |
| IntArrayList enclosingTypeIds = typeEnvironment.getEnclosingTypeIdsOfExportedMethods(); |
| for (int i = 0; i < enclosingTypeIds.size(); i++) { |
| int enclosingTypeId = enclosingTypeIds.get(i); |
| IntArrayList exportedMethodIds = |
| typeEnvironment.getExportedMemberMethodIdsIn(enclosingTypeId); |
| if (exportedMethodIds == null) { |
| continue; |
| } |
| |
| for (int j = 0; j < exportedMethodIds.size(); j++) { |
| int exportedMethodId = exportedMethodIds.get(j); |
| rapidTypeAnalyzer.markMethodIdReachable(exportedMethodId, false); |
| } |
| } |
| |
| // Artificially reach types with @JsExport'ed or @JsType'ed static fields or methods (including |
| // Constructors). These types will not be pruned. |
| IntArrayList typeIdsWithExportedStaticReferences = |
| typeEnvironment.getTypeIdsWithExportedStaticReferences(); |
| for (int i = 0; i < typeIdsWithExportedStaticReferences.size(); i++) { |
| int typeId = typeIdsWithExportedStaticReferences.get(i); |
| rapidTypeAnalyzer.markTypeIdReachable(typeId); |
| String typeName = typeEnvironment.getTypeNameById(typeId); |
| int typeClinitMethodId = typeEnvironment.getMethodIdByName(typeName + "::$clinit()V"); |
| rapidTypeAnalyzer.markMethodIdReachable(typeClinitMethodId, false); |
| } |
| |
| // Reach and traverse entry method types. This is just the EntryMethodHolder |
| // class and its init() function. It will not be pruned. This is the conceptual "root" of the |
| // application execution. |
| IntArrayList entryMethodIds = typeEnvironment.getEntryMethodIds(); |
| for (int i = 0; i < entryMethodIds.size(); i++) { |
| int entryMethodId = entryMethodIds.get(i); |
| int typeId = typeEnvironment.getEnclosingTypeId(entryMethodId); |
| rapidTypeAnalyzer.markTypeIdReachable(typeId); |
| // Includes the clinit method. |
| rapidTypeAnalyzer.markMemberMethodIdsReachable(typeId); |
| } |
| |
| // Perform rapid type analysis. |
| IntArrayList reachableTypeIds = rapidTypeAnalyzer.computeReachableTypeIds(); |
| |
| // Translate ids back to strings and keep the results around for the next compile. |
| Set<String> reachableTypeNames = Sets.newHashSet(); |
| for (int i = 0; i < reachableTypeIds.size(); i++) { |
| int reachableTypeId = reachableTypeIds.get(i); |
| reachableTypeNames.add(typeEnvironment.getTypeNameById(reachableTypeId)); |
| } |
| |
| copyCollection(reachableTypeNames, lastReachableTypeNames); |
| return reachableTypeNames; |
| } |
| |
| /** |
| * Replaces the contents of this cache with the contents of the given cache. |
| * <p> |
| * This operation should be kept fast as it will be called once per compile. At the moment it |
| * takes about 2.5% of the time in an incremental recompile. If wanting to recover this time in |
| * the future consider parallelizing the copy or grouping the values from hash tables with similar |
| * keys into a single data object and hashtable so that fewer references need to be replicated. |
| */ |
| public void copyFrom(MinimalRebuildCache that) { |
| this.lastLinkedJsBytes = that.lastLinkedJsBytes; |
| |
| this.intTypeMapper.copyFrom(that.intTypeMapper); |
| this.jsIncrementalNamerState.copyFrom(that.jsIncrementalNamerState); |
| this.immediateTypeRelations.copyFrom(that.immediateTypeRelations); |
| this.typeEnvironment.copyFrom(that.typeEnvironment); |
| |
| copyMap(that.compilationUnitTypeNameByNestedTypeName, |
| this.compilationUnitTypeNameByNestedTypeName); |
| copyMap(that.contentHashByGeneratedTypeName, this.contentHashByGeneratedTypeName); |
| copyMap(that.descriptionByexportedGlobalNames, this.descriptionByexportedGlobalNames); |
| copyMap(that.jsByTypeName, this.jsByTypeName); |
| copyMap(that.lastModifiedByDiskSourcePath, this.lastModifiedByDiskSourcePath); |
| copyMap(that.lastModifiedByResourcePath, this.lastModifiedByResourcePath); |
| copyMap(that.sourceMapsByTypeName, this.sourceMapsByTypeName); |
| copyMap(that.statementRangesByTypeName, this.statementRangesByTypeName); |
| |
| copyMultimap(that.exportedGlobalNamesByTypeName, this.exportedGlobalNamesByTypeName); |
| copyMultimap(that.generatedCompilationUnitNamesByReboundTypeNames, |
| this.generatedCompilationUnitNamesByReboundTypeNames); |
| copyMultimap(that.nestedTypeNamesByUnitTypeName, this.nestedTypeNamesByUnitTypeName); |
| copyMultimap(that.rebinderTypeNamesByReboundTypeName, this.rebinderTypeNamesByReboundTypeName); |
| copyMultimap(that.reboundTypeNamesByGeneratedCompilationUnitNames, |
| this.reboundTypeNamesByGeneratedCompilationUnitNames); |
| copyMultimap(that.reboundTypeNamesByInputResource, this.reboundTypeNamesByInputResource); |
| copyMultimap(that.referencedTypeNamesByTypeName, this.referencedTypeNamesByTypeName); |
| copyMultimap(that.typeNamesByReferencingTypeName, this.typeNamesByReferencingTypeName); |
| |
| copyCollection(that.deletedCompilationUnitNames, this.deletedCompilationUnitNames); |
| copyCollection(that.deletedDiskSourcePaths, this.deletedDiskSourcePaths); |
| copyCollection(that.deletedResourcePaths, this.deletedResourcePaths); |
| copyCollection(that.dualJsoImplInterfaceNames, this.dualJsoImplInterfaceNames); |
| copyCollection(that.generatedArtifacts, this.generatedArtifacts); |
| copyCollection(that.jsoStatusChangedTypeNames, this.jsoStatusChangedTypeNames); |
| copyCollection(that.jsoTypeNames, this.jsoTypeNames); |
| copyCollection(that.lastReachableTypeNames, this.lastReachableTypeNames); |
| copyCollection(that.modifiedCompilationUnitNames, this.modifiedCompilationUnitNames); |
| copyCollection(that.modifiedDiskSourcePaths, this.modifiedDiskSourcePaths); |
| copyCollection(that.modifiedResourcePaths, this.modifiedResourcePaths); |
| copyCollection(that.preambleTypeNames, this.preambleTypeNames); |
| copyCollection(that.rootTypeNames, this.rootTypeNames); |
| copyCollection(that.singleJsoImplInterfaceNames, this.singleJsoImplInterfaceNames); |
| copyCollection(that.sourceCompilationUnitNames, this.sourceCompilationUnitNames); |
| copyCollection(that.staleTypeNames, this.staleTypeNames); |
| } |
| |
| /** |
| * Return the set of provided typeNames with unreachable types filtered out. |
| */ |
| public Set<String> filterUnreachableTypeNames(Set<String> typeNames) { |
| return Sets.newHashSet(Sets.filter(typeNames, Predicates.in(lastReachableTypeNames))); |
| } |
| |
| public ArtifactSet getGeneratedArtifacts() { |
| return generatedArtifacts; |
| } |
| |
| public ImmediateTypeRelations getImmediateTypeRelations() { |
| return immediateTypeRelations; |
| } |
| |
| public String getJs(String typeName) { |
| return jsByTypeName.get(typeName); |
| } |
| |
| public int getLastLinkedJsBytes() { |
| return lastLinkedJsBytes; |
| } |
| |
| @VisibleForTesting |
| public Set<String> getModifiedCompilationUnitNames() { |
| return modifiedCompilationUnitNames; |
| } |
| |
| public JsIncrementalNamerState getPersistentPrettyNamerState() { |
| return jsIncrementalNamerState; |
| } |
| |
| public Set<String> getPreambleTypeNames() { |
| return preambleTypeNames; |
| } |
| |
| /** |
| * Returns the set of the names of types that were processed as stale. This list can be larger |
| * than the calculated list of stale types for example when a new reference is created to a type |
| * that had not been processed in a previous compile. |
| */ |
| public Set<String> getProcessedStaleTypeNames() { |
| return processedStaleTypeNames; |
| } |
| |
| public JsSourceMap getSourceMap(String typeName) { |
| return sourceMapsByTypeName.get(typeName); |
| } |
| |
| @VisibleForTesting |
| public Set<String> getStaleTypeNames() { |
| return staleTypeNames; |
| } |
| |
| public StatementRanges getStatementRanges(String typeName) { |
| return statementRangesByTypeName.get(typeName); |
| } |
| |
| public StringAnalyzableTypeEnvironment getTypeEnvironment() { |
| return typeEnvironment; |
| } |
| |
| public IntTypeMapper getTypeMapper() { |
| return intTypeMapper; |
| } |
| |
| public boolean hasJs(String typeName) { |
| return jsByTypeName.containsKey(typeName); |
| } |
| |
| public boolean hasPreambleTypeNames() { |
| return !preambleTypeNames.isEmpty(); |
| } |
| |
| /** |
| * Returns true if there is cached data to reuse in the next recompile. |
| */ |
| public boolean isPopulated() { |
| return !immediateTypeRelations.isEmpty(); |
| } |
| |
| public boolean isSourceCompilationUnit(String compilationUnitName) { |
| return sourceCompilationUnitNames.contains(compilationUnitName); |
| } |
| |
| public boolean knowsLastLinkedJsBytes() { |
| return lastLinkedJsBytes != null; |
| } |
| |
| @VisibleForTesting |
| public void markSourceFileStale(String typeName) { |
| lastModifiedByDiskSourcePath.remove(typeName); |
| } |
| |
| /** |
| * Records the resource paths and modification dates of build resources in the current compile and |
| * builds a list of known modified resource paths by comparing the resource paths and modification |
| * dates of build resources in the previous compile with those of the current compile. |
| */ |
| public void recordBuildResources(ModuleDef module) { |
| Map<String, Long> currentModifiedByResourcePath = |
| resourcesToModifiedByPath(module.getBuildResourceOracle().getResources()); |
| recordModifiedResources(currentModifiedByResourcePath, lastModifiedByResourcePath, |
| modifiedResourcePaths, deletedResourcePaths); |
| } |
| |
| /** |
| * Records the paths and modification dates of source resources in the current compile and builds |
| * a list of known modified paths by comparing the paths and modification dates of source |
| * resources in the previous compile with those of the current compile. |
| */ |
| @VisibleForTesting |
| public void recordDiskSourceResources(Map<String, Long> currentModifiedByDiskSourcePath) { |
| recordModifiedResources(currentModifiedByDiskSourcePath, lastModifiedByDiskSourcePath, |
| modifiedDiskSourcePaths, deletedDiskSourcePaths); |
| |
| deletedCompilationUnitNames.clear(); |
| deletedCompilationUnitNames.addAll(resourcePathsToCompilationUnitNames(deletedDiskSourcePaths)); |
| modifiedCompilationUnitNames.clear(); |
| modifiedCompilationUnitNames.addAll( |
| resourcePathsToCompilationUnitNames(modifiedDiskSourcePaths)); |
| } |
| |
| /** |
| * Records the paths and modification dates of source resources in the current compile and builds |
| * a list of known modified paths by comparing the paths and modification dates of source |
| * resources in the previous compile with those of the current compile. |
| */ |
| public void recordDiskSourceResources(ModuleDef module) { |
| Map<String, Long> currentModifiedByDiskSourcePath = |
| resourcesToModifiedByPath(module.getSourceResourceOracle().getResources()); |
| recordDiskSourceResources(currentModifiedByDiskSourcePath); |
| } |
| |
| /** |
| * Records the paths and content ids of generated source resources in the current compile and |
| * updates a list of known modified paths by comparing the paths and content ids of generated |
| * source resources in the previous compile with those of the current compile. |
| */ |
| public void recordGeneratedUnits(Collection<GeneratedUnit> generatedUnits) { |
| // Not all Generators are run on each compile so it is not possible to compare previous and |
| // current generated units to detect deletions. As a result only modifications are tracked. |
| |
| for (GeneratedUnit generatedUnit : generatedUnits) { |
| String currentStrongHash = generatedUnit.getStrongHash(); |
| String lastKnownStrongHash = |
| contentHashByGeneratedTypeName.put(generatedUnit.getTypeName(), currentStrongHash); |
| if (!Objects.equal(lastKnownStrongHash, currentStrongHash)) { |
| // Added or Modified resource. |
| modifiedCompilationUnitNames.add(generatedUnit.getTypeName()); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public void recordNestedTypeName(String compilationUnitTypeName, String nestedTypeName) { |
| nestedTypeNamesByUnitTypeName.put(compilationUnitTypeName, nestedTypeName); |
| compilationUnitTypeNameByNestedTypeName.put(nestedTypeName, compilationUnitTypeName); |
| } |
| |
| public void recordNestedTypeNamesPerType(CompilationUnit compilationUnit) { |
| // For the root type in the compilation unit the source name and binary name are the same. |
| String compilationUnitTypeName = compilationUnit.getTypeName(); |
| |
| nestedTypeNamesByUnitTypeName.removeAll(compilationUnitTypeName); |
| for (CompiledClass compiledClass : compilationUnit.getCompiledClasses()) { |
| String nestedTypeName = InternalName.toBinaryName(compiledClass.getInternalName()); |
| recordNestedTypeName(compilationUnitTypeName, nestedTypeName); |
| } |
| } |
| |
| /** |
| * Records that rebinder type Foo contains a GWT.create(ReboundTypeBar.class) call. |
| */ |
| public void recordRebinderTypeForReboundType(String reboundTypeName, String rebinderType) { |
| rebinderTypeNamesByReboundTypeName.put(reboundTypeName, rebinderType); |
| } |
| |
| public void removeExportedNames(String inTypeName) { |
| Collection<String> exportedGlobalNamesForType = |
| exportedGlobalNamesByTypeName.removeAll(inTypeName); |
| descriptionByexportedGlobalNames.keySet().removeAll(exportedGlobalNamesForType); |
| } |
| |
| public void removeReferencesFrom(String fromTypeName) { |
| Collection<String> toTypeNames = referencedTypeNamesByTypeName.get(fromTypeName); |
| for (String toTypeName : toTypeNames) { |
| typeNamesByReferencingTypeName.remove(toTypeName, fromTypeName); |
| } |
| referencedTypeNamesByTypeName.removeAll(fromTypeName); |
| } |
| |
| public void setEntryMethodNames(List<String> entryMethodNames) { |
| typeEnvironment.setEntryMethodNames(entryMethodNames); |
| } |
| |
| public void setJsForType(TreeLogger logger, String typeName, String typeJs) { |
| logger.log(TreeLogger.SPAM, "caching JS for type " + typeName); |
| jsByTypeName.put(typeName, typeJs); |
| } |
| |
| /** |
| * Records the names of JSO subtypes, single impl interfaces and dual impl interfaces as well as |
| * keeps track of types that entered or left one of the lists in the most recent iteration. |
| */ |
| public void setJsoTypeNames(Set<String> jsoTypeNames, Set<String> singleJsoImplInterfaceNames, |
| Set<String> dualJsoImplInterfaceNames) { |
| this.jsoStatusChangedTypeNames.clear(); |
| this.jsoStatusChangedTypeNames.addAll( |
| Sets.symmetricDifference(this.jsoTypeNames, jsoTypeNames)); |
| this.jsoStatusChangedTypeNames.addAll( |
| Sets.symmetricDifference(this.singleJsoImplInterfaceNames, singleJsoImplInterfaceNames)); |
| this.jsoStatusChangedTypeNames.addAll( |
| Sets.symmetricDifference(this.dualJsoImplInterfaceNames, dualJsoImplInterfaceNames)); |
| |
| this.jsoTypeNames.clear(); |
| this.jsoTypeNames.addAll(jsoTypeNames); |
| this.singleJsoImplInterfaceNames.clear(); |
| this.singleJsoImplInterfaceNames.addAll(singleJsoImplInterfaceNames); |
| this.dualJsoImplInterfaceNames.clear(); |
| this.dualJsoImplInterfaceNames.addAll(dualJsoImplInterfaceNames); |
| } |
| |
| public void setLastLinkedJsBytes(int lastLinkedJsBytes) { |
| this.lastLinkedJsBytes = lastLinkedJsBytes; |
| } |
| |
| public void setPreambleTypeNames(TreeLogger logger, Set<String> preambleTypeNames) { |
| logger.log(TreeLogger.DEBUG, "caching list of known preamble types " + preambleTypeNames); |
| this.preambleTypeNames.addAll(preambleTypeNames); |
| } |
| |
| public void setProcessedStaleTypeNames(Set<String> processedStaleTypeNames) { |
| // If this is the first compile. |
| if (!isPopulated()) { |
| // Don't record processed stale types, since the list is huge and not useful for test |
| // assertions. |
| return; |
| } |
| |
| this.processedStaleTypeNames = ImmutableSet.copyOf(processedStaleTypeNames); |
| } |
| |
| public void setRootTypeNames(Collection<String> rootTypeNames) { |
| this.rootTypeNames.clear(); |
| this.rootTypeNames.addAll(rootTypeNames); |
| } |
| |
| public void setSourceMapForType(String typeName, JsSourceMap sourceMap) { |
| sourceMapsByTypeName.put(typeName, sourceMap); |
| } |
| |
| public void setStatementRangesForType(String typeName, StatementRanges statementRanges) { |
| statementRangesByTypeName.put(typeName, statementRanges); |
| } |
| |
| @VisibleForTesting |
| boolean hasSameContent(MinimalRebuildCache that) { |
| // Ignoring processedStaleTypeNames since it is transient. |
| return this.immediateTypeRelations.hasSameContent(that.immediateTypeRelations) && Objects.equal( |
| this.compilationUnitTypeNameByNestedTypeName, that.compilationUnitTypeNameByNestedTypeName) |
| && Objects.equal(this.contentHashByGeneratedTypeName, that.contentHashByGeneratedTypeName) |
| && Objects.equal(this.deletedCompilationUnitNames, that.deletedCompilationUnitNames) |
| && Objects.equal(this.deletedDiskSourcePaths, that.deletedDiskSourcePaths) |
| && Objects.equal(this.deletedResourcePaths, that.deletedResourcePaths) |
| && Objects.equal(this.dualJsoImplInterfaceNames, that.dualJsoImplInterfaceNames) |
| && Objects.equal(this.generatedArtifacts, that.generatedArtifacts) |
| && Objects.equal(this.descriptionByexportedGlobalNames, |
| that.descriptionByexportedGlobalNames) |
| && Objects.equal(this.exportedGlobalNamesByTypeName, that.exportedGlobalNamesByTypeName) |
| && Objects.equal(this.generatedCompilationUnitNamesByReboundTypeNames, |
| that.generatedCompilationUnitNamesByReboundTypeNames) |
| && this.intTypeMapper.hasSameContent(that.intTypeMapper) |
| && Objects.equal(this.jsByTypeName, that.jsByTypeName) |
| && Objects.equal(this.jsoStatusChangedTypeNames, that.jsoStatusChangedTypeNames) |
| && Objects.equal(this.jsoTypeNames, that.jsoTypeNames) |
| && Objects.equal(this.lastLinkedJsBytes, that.lastLinkedJsBytes) |
| && Objects.equal(this.lastModifiedByDiskSourcePath, that.lastModifiedByDiskSourcePath) |
| && Objects.equal(this.lastModifiedByResourcePath, that.lastModifiedByResourcePath) |
| && Objects.equal(this.lastReachableTypeNames, that.lastReachableTypeNames) |
| && Objects.equal(this.modifiedCompilationUnitNames, that.modifiedCompilationUnitNames) |
| && Objects.equal(this.modifiedDiskSourcePaths, that.modifiedDiskSourcePaths) |
| && Objects.equal(this.modifiedResourcePaths, that.modifiedResourcePaths) |
| && Objects.equal(this.nestedTypeNamesByUnitTypeName, that.nestedTypeNamesByUnitTypeName) |
| && this.jsIncrementalNamerState.hasSameContent(that.jsIncrementalNamerState) |
| && Objects.equal(this.preambleTypeNames, that.preambleTypeNames) && Objects.equal( |
| this.rebinderTypeNamesByReboundTypeName, that.rebinderTypeNamesByReboundTypeName) |
| && Objects.equal(this.reboundTypeNamesByGeneratedCompilationUnitNames, |
| that.reboundTypeNamesByGeneratedCompilationUnitNames) |
| && Objects.equal(this.reboundTypeNamesByInputResource, that.reboundTypeNamesByInputResource) |
| && Objects.equal(this.referencedTypeNamesByTypeName, that.referencedTypeNamesByTypeName) |
| && Objects.equal(this.rootTypeNames, that.rootTypeNames) |
| && Objects.equal(this.singleJsoImplInterfaceNames, that.singleJsoImplInterfaceNames) |
| && Objects.equal(this.sourceCompilationUnitNames, that.sourceCompilationUnitNames) |
| && Objects.equal(this.sourceMapsByTypeName, that.sourceMapsByTypeName) |
| && Objects.equal(this.staleTypeNames, that.staleTypeNames) |
| && Objects.equal(this.statementRangesByTypeName, that.statementRangesByTypeName) |
| && this.typeEnvironment.hasSameContent(that.typeEnvironment) |
| && Objects.equal(this.typeNamesByReferencingTypeName, that.typeNamesByReferencingTypeName); |
| } |
| |
| private void appendReferencingTypes(Set<String> accumulatedTypeNames, |
| Collection<String> referencedTypeNames) { |
| for (String referencedTypeName : referencedTypeNames) { |
| Collection<String> referencingTypeNames = |
| typeNamesByReferencingTypeName.get(referencedTypeName); |
| accumulatedTypeNames.addAll(referencingTypeNames); |
| } |
| } |
| |
| /** |
| * If type Foo is a generated type and is stale this pass will append type Bar that triggers |
| * Generator Baz that regenerates type Foo. |
| * <p> |
| * This is necessary since just clearing the cache for type Foo would not be adequate to cause the |
| * recreation of its cached JS without also rerunning the Generator that creates type Foo. |
| */ |
| private void appendTypesToRegenerateStaleGeneratedTypes(Set<String> staleTypeNames) { |
| Set<String> generatedCompilationUnitNames = |
| reboundTypeNamesByGeneratedCompilationUnitNames.keySet(); |
| |
| // Filter the current stale types list for any compilation units that are known to be generated. |
| Set<String> staleGeneratedCompilationUnitNames = Sets.intersection( |
| computeCompilationUnitNames(staleTypeNames), generatedCompilationUnitNames); |
| boolean discoveredMoreStaleTypes; |
| do { |
| // Accumulate staleGeneratedCompilationUnits -> generators -> |
| // generatorTriggeringCompilationUnits. |
| Set<String> reboundTypesThatGenerateTheStaleCompilationUnits = |
| computeReboundTypesThatGenerateTypes(staleGeneratedCompilationUnitNames); |
| Set<String> generatorTriggeringTypes = |
| computeTypesThatRebindTypes(reboundTypesThatGenerateTheStaleCompilationUnits); |
| // Mark these generator triggering types stale and keep track of whether any of them were not |
| // previously known to be stale. |
| discoveredMoreStaleTypes = staleTypeNames.addAll(generatorTriggeringTypes); |
| |
| // It's possible that a generator triggering type was itself also created by a Generator. |
| // Repeat the backwards trace process till none of the newly stale types are generated types. |
| staleGeneratedCompilationUnitNames = Sets.intersection( |
| computeCompilationUnitNames(generatorTriggeringTypes), generatedCompilationUnitNames); |
| } while (discoveredMoreStaleTypes); |
| } |
| |
| private void clearCachedTypeOutput(String staleTypeName) { |
| jsByTypeName.remove(staleTypeName); |
| statementRangesByTypeName.remove(staleTypeName); |
| sourceMapsByTypeName.remove(staleTypeName); |
| } |
| |
| /** |
| * Returns the set of names of CompilationUnits that contain all of the given type names. |
| */ |
| private Set<String> computeCompilationUnitNames(Set<String> typeNames) { |
| Set<String> compilationUnitNames = Sets.newHashSet(); |
| for (String typeName : typeNames) { |
| compilationUnitNames.add(compilationUnitTypeNameByNestedTypeName.get(typeName)); |
| } |
| return compilationUnitNames; |
| } |
| |
| /** |
| * Returns the set of type names contained within the given compilation units. |
| */ |
| private Set<String> computeNestedTypeNames(Set<String> compilationUnitNames) { |
| Set<String> nestedTypeNames = Sets.newHashSet(); |
| nestedTypeNames.addAll(compilationUnitNames); |
| for (String compilationUnitName : compilationUnitNames) { |
| nestedTypeNames.addAll(nestedTypeNamesByUnitTypeName.get(compilationUnitName)); |
| } |
| return nestedTypeNames; |
| } |
| |
| /** |
| * Returns the set of names of types that when rebound trigger Generators that access resources |
| * which are known to have been modified. |
| */ |
| private Set<String> computeReboundTypesAffectedByModifiedResources() { |
| Set<String> affectedRebindTypeNames = Sets.newHashSet(); |
| for (String modifiedResourcePath : modifiedResourcePaths) { |
| affectedRebindTypeNames.addAll(reboundTypeNamesByInputResource.get(modifiedResourcePath)); |
| } |
| return affectedRebindTypeNames; |
| } |
| |
| private Set<String> computeReboundTypesThatGenerateTypes( |
| Set<String> staleGeneratedCompilationUnitNames) { |
| Set<String> reboundTypesThatGenerateTypes = Sets.newHashSet(); |
| for (String staleGeneratedCompilationUnitName : staleGeneratedCompilationUnitNames) { |
| reboundTypesThatGenerateTypes.addAll( |
| reboundTypeNamesByGeneratedCompilationUnitNames.get(staleGeneratedCompilationUnitName)); |
| } |
| return reboundTypesThatGenerateTypes; |
| } |
| |
| /** |
| * Returns the set of names of types that contain GWT.create(ReboundType.class) calls that rebind |
| * the given set of type names. |
| */ |
| private Set<String> computeTypesThatRebindTypes(Set<String> reboundTypeNames) { |
| Set<String> typesThatRebindTypes = Sets.newHashSet(); |
| for (String reboundTypeName : reboundTypeNames) { |
| typesThatRebindTypes.addAll(rebinderTypeNamesByReboundTypeName.get(reboundTypeName)); |
| } |
| return typesThatRebindTypes; |
| } |
| } |