| /* |
| * Copyright 2009 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.javac; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.dev.javac.CompilationUnitBuilder.GeneratedCompilationUnitBuilder; |
| import com.google.gwt.dev.javac.CompilationUnitBuilder.ResourceCompilationUnitBuilder; |
| import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate; |
| import com.google.gwt.dev.javac.JdtCompiler.UnitProcessor; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.resource.Resource; |
| import com.google.gwt.dev.util.StringInterner; |
| import com.google.gwt.dev.util.log.speedtracer.DevModeEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| |
| import org.apache.commons.collections.map.AbstractReferenceMap; |
| import org.apache.commons.collections.map.ReferenceIdentityMap; |
| import org.apache.commons.collections.map.ReferenceMap; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Map.Entry; |
| |
| /** |
| * Manages a centralized cache for compiled units. |
| */ |
| public class CompilationStateBuilder { |
| |
| /** |
| * An opaque class that lets you compile more units later. |
| */ |
| public class CompileMoreLater { |
| |
| private final class UnitProcessorImpl implements UnitProcessor { |
| |
| public void process(CompilationUnitBuilder builder, |
| CompilationUnitDeclaration cud, List<CompiledClass> compiledClasses) { |
| Map<AbstractMethodDeclaration, JsniMethod> jsniMethods = JsniCollector.collectJsniMethods( |
| cud, builder.getSource(), jsProgram); |
| |
| // JSNI check + collect dependencies. |
| final Set<String> jsniDeps = new HashSet<String>(); |
| JsniChecker.check(cud, jsniMethods, new JsniChecker.TypeResolver() { |
| public ReferenceBinding resolveType(String typeName) { |
| ReferenceBinding resolveType = compiler.resolveType(typeName); |
| if (resolveType != null) { |
| jsniDeps.add(String.valueOf(resolveType.qualifiedSourceName())); |
| } |
| return resolveType; |
| } |
| }); |
| |
| JSORestrictionsChecker.check(jsoState, cud); |
| ArtificialRescueChecker.check(cud, builder.isGenerated()); |
| BinaryTypeReferenceRestrictionsChecker.check(cud); |
| |
| MethodArgNamesLookup methodArgs = MethodParamCollector.collect(cud); |
| |
| StringInterner interner = StringInterner.get(); |
| String packageName = interner.intern( |
| Shared.getPackageName(builder.getTypeName())); |
| List<String> unresolvedQualified = new ArrayList<String>(); |
| List<String> unresolvedSimple = new ArrayList<String>(); |
| for (char[] simpleRef : cud.compilationResult().simpleNameReferences) { |
| unresolvedSimple.add(interner.intern(String.valueOf(simpleRef))); |
| } |
| for (char[][] qualifiedRef : |
| cud.compilationResult().qualifiedReferences) { |
| unresolvedQualified.add( |
| interner.intern(CharOperation.toString(qualifiedRef))); |
| } |
| for (String jsniDep : jsniDeps) { |
| unresolvedQualified.add(interner.intern(jsniDep)); |
| } |
| ArrayList<String> apiRefs = compiler.collectApiRefs(cud); |
| for (int i = 0; i < apiRefs.size(); ++i) { |
| apiRefs.set(i, interner.intern(apiRefs.get(i))); |
| } |
| Dependencies dependencies = new Dependencies(packageName, |
| unresolvedQualified, unresolvedSimple, apiRefs); |
| |
| CompilationUnit unit = builder.build(compiledClasses, dependencies, |
| jsniMethods.values(), methodArgs, |
| cud.compilationResult().getProblems()); |
| addValidUnit(unit); |
| // Cache the valid unit for future compiles. |
| ContentId contentId = builder.getContentId(); |
| unitCache.put(contentId, unit); |
| if (builder instanceof ResourceCompilationUnitBuilder) { |
| ResourceCompilationUnitBuilder rcub = (ResourceCompilationUnitBuilder) builder; |
| ResourceTag resourceTag = new ResourceTag(rcub.getLastModifed(), |
| contentId); |
| resourceContentCache.put(builder.getLocation(), resourceTag); |
| keepAliveLatestVersion.put(resourceTag, unit); |
| } else if (builder instanceof GeneratedCompilationUnitBuilder) { |
| keepAliveRecentlyGenerated.put(unit.getTypeName(), unit); |
| } |
| newlyBuiltUnits.add(unit); |
| } |
| } |
| |
| /** |
| * A global cache of all currently-valid class files. This is used to |
| * validate dependencies when reusing previously cached units, to make sure |
| * they can be recompiled if necessary. |
| */ |
| private final Map<String, CompiledClass> allValidClasses = new HashMap<String, CompiledClass>(); |
| |
| /** |
| * The JDT compiler. |
| */ |
| private final JdtCompiler compiler = new JdtCompiler( |
| new UnitProcessorImpl()); |
| |
| /** |
| * Continuation state for JSNI checking. |
| */ |
| private final JSORestrictionsChecker.CheckerState jsoState = new JSORestrictionsChecker.CheckerState(); |
| |
| private transient Collection<CompilationUnit> newlyBuiltUnits; |
| |
| public CompileMoreLater(AdditionalTypeProviderDelegate delegate) { |
| compiler.setAdditionalTypeProviderDelegate(delegate); |
| } |
| |
| public Collection<CompilationUnit> addGeneratedTypes(TreeLogger logger, |
| Collection<GeneratedUnit> generatedUnits) { |
| Event compilationStateBuilderProcess = |
| SpeedTracerLogger.start(DevModeEventType.COMPILATION_STATE_BUILDER_PROCESS); |
| try { |
| return doBuildGeneratedTypes(logger, generatedUnits, this); |
| } finally { |
| compilationStateBuilderProcess.end(); |
| } |
| } |
| |
| public Map<String, CompiledClass> getValidClasses() { |
| return Collections.unmodifiableMap(allValidClasses); |
| } |
| |
| void addValidUnit(CompilationUnit unit) { |
| compiler.addCompiledUnit(unit); |
| for (CompiledClass cc : unit.getCompiledClasses()) { |
| String sourceName = cc.getSourceName(); |
| allValidClasses.put(sourceName, cc); |
| } |
| } |
| |
| Collection<CompilationUnit> compile(TreeLogger logger, |
| Collection<CompilationUnitBuilder> builders, |
| Map<CompilationUnitBuilder, CompilationUnit> cachedUnits) { |
| // Initialize the set of valid classes to the initially cached units. |
| for (CompilationUnit unit : cachedUnits.values()) { |
| for (CompiledClass cc : unit.getCompiledClasses()) { |
| // Map by source name. |
| String sourceName = cc.getSourceName(); |
| allValidClasses.put(sourceName, cc); |
| } |
| } |
| Map<CompiledClass, CompiledClass> cachedStructurallySame = new IdentityHashMap<CompiledClass, CompiledClass>(); |
| |
| ArrayList<CompilationUnit> resultUnits = new ArrayList<CompilationUnit>(); |
| do { |
| // Compile anything that needs to be compiled. |
| this.newlyBuiltUnits = new ArrayList<CompilationUnit>(); |
| |
| compiler.doCompile(builders); |
| resultUnits.addAll(this.newlyBuiltUnits); |
| builders.clear(); |
| |
| // Resolve all newly built unit deps against the global classes. |
| for (CompilationUnit unit : this.newlyBuiltUnits) { |
| unit.getDependencies().resolve(allValidClasses); |
| } |
| |
| /* |
| * Invalidate any cached units with invalid refs. |
| */ |
| Collection<CompilationUnit> invalidatedUnits = new ArrayList<CompilationUnit>(); |
| for (Iterator<Entry<CompilationUnitBuilder, CompilationUnit>> it = cachedUnits.entrySet().iterator(); it.hasNext();) { |
| Entry<CompilationUnitBuilder, CompilationUnit> entry = it.next(); |
| CompilationUnit unit = entry.getValue(); |
| boolean isValid = unit.getDependencies().validate(allValidClasses, |
| cachedStructurallySame); |
| if (!isValid) { |
| invalidatedUnits.add(unit); |
| builders.add(entry.getKey()); |
| it.remove(); |
| } |
| } |
| |
| // Any units we invalidated must now be removed from the valid classes. |
| for (CompilationUnit unit : invalidatedUnits) { |
| for (CompiledClass cc : unit.getCompiledClasses()) { |
| allValidClasses.remove(cc.getSourceName()); |
| } |
| } |
| } while (builders.size() > 0); |
| |
| // Any remaining cached units are valid now. |
| resultUnits.addAll(cachedUnits.values()); |
| |
| // Sort, then report all errors (re-report for cached units). |
| Collections.sort(resultUnits, CompilationUnit.COMPARATOR); |
| logger = logger.branch(TreeLogger.DEBUG, |
| "Validating newly compiled units"); |
| for (CompilationUnit unit : resultUnits) { |
| CompilationUnitInvalidator.reportErrors(logger, unit); |
| } |
| return resultUnits; |
| } |
| } |
| |
| /** |
| * A snapshot of a source file at a particular point in time. |
| */ |
| static class ResourceTag { |
| /** |
| * A Java type name + content hash. E.g. |
| * <code>"java.lang.String:1234DEADBEEF"</code>. |
| */ |
| private final ContentId contentId; |
| /** |
| * The last modification time, for quick freshness checks. |
| */ |
| private final long lastModified; |
| |
| public ResourceTag(long lastModified, ContentId contentId) { |
| this.lastModified = lastModified; |
| this.contentId = contentId; |
| } |
| |
| public ContentId getContentId() { |
| return contentId; |
| } |
| |
| public long getLastModified() { |
| return lastModified; |
| } |
| } |
| |
| private static final CompilationStateBuilder instance = new CompilationStateBuilder(); |
| |
| public static CompilationState buildFrom(TreeLogger logger, |
| Set<Resource> resources) { |
| Event compilationStateBuilderProcessEvent = |
| SpeedTracerLogger.start(DevModeEventType.COMPILATION_STATE_BUILDER_PROCESS); |
| try { |
| return instance.doBuildFrom(logger, resources, null); |
| } finally { |
| compilationStateBuilderProcessEvent.end(); |
| } |
| } |
| |
| public static CompilationState buildFrom(TreeLogger logger, |
| Set<Resource> resources, AdditionalTypeProviderDelegate delegate) { |
| Event compilationStateBuilderProcessEvent = |
| SpeedTracerLogger.start(DevModeEventType.COMPILATION_STATE_BUILDER_PROCESS); |
| try { |
| return instance.doBuildFrom(logger, resources, delegate); |
| } finally { |
| compilationStateBuilderProcessEvent.end(); |
| } |
| } |
| |
| public static CompilationStateBuilder get() { |
| return instance; |
| } |
| |
| /** |
| * JsProgram for collecting JSNI methods. |
| */ |
| private final JsProgram jsProgram = new JsProgram(); |
| |
| /** |
| * This map of weak keys to hard values exists solely to keep the most recent |
| * version of any unit from being eagerly garbage collected. |
| * |
| * Once a resource gets its tag updated, the {@link #resourceContentCache} |
| * will drop the only hard reference to that resourceTag which transitively |
| * removes the old value from this cache. |
| * |
| * WRITE-ONLY |
| */ |
| @SuppressWarnings("unchecked") |
| private final Map<ResourceTag, CompilationUnit> keepAliveLatestVersion = Collections.synchronizedMap(new ReferenceIdentityMap( |
| AbstractReferenceMap.WEAK, AbstractReferenceMap.HARD)); |
| |
| /** |
| * This map of hard keys to soft values exists solely to keep the most |
| * recently generated version of a type from being eagerly garbage collected. |
| * |
| * WRITE-ONLY |
| */ |
| @SuppressWarnings("unchecked") |
| private final Map<String, CompilationUnit> keepAliveRecentlyGenerated = Collections.synchronizedMap(new ReferenceMap( |
| AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT)); |
| |
| /** |
| * Holds a tag for the last seen content for a each resource location. Should |
| * be bounded by the total number of accessible Java source files on the |
| * system that we try to compile. |
| */ |
| private final Map<String, ResourceTag> resourceContentCache = Collections.synchronizedMap(new HashMap<String, ResourceTag>()); |
| |
| /** |
| * A map of all previously compiled units by contentId. |
| * |
| * TODO: ideally this would be a list of units because the same unit can be |
| * compiled multiple ways based on its dependencies. |
| */ |
| @SuppressWarnings("unchecked") |
| private final Map<ContentId, CompilationUnit> unitCache = Collections.synchronizedMap(new ReferenceMap( |
| AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)); |
| |
| /** |
| * Build a new compilation state from a source oracle. |
| */ |
| public CompilationState doBuildFrom(TreeLogger logger, |
| Set<Resource> resources) { |
| return doBuildFrom(logger, resources, null); |
| } |
| |
| /** |
| * Build a new compilation state from a source oracle. Allow the caller to specify |
| * a compiler delegate that will handle undefined names. |
| * |
| * TODO: maybe use a finer brush than to synchronize the whole thing. |
| */ |
| public synchronized CompilationState doBuildFrom(TreeLogger logger, |
| Set<Resource> resources, AdditionalTypeProviderDelegate compilerDelegate) { |
| Event compilationStateBuilderProcess = |
| SpeedTracerLogger.start(DevModeEventType.COMPILATION_STATE_BUILDER_PROCESS); |
| |
| try { |
| // Units we definitely want to build. |
| List<CompilationUnitBuilder> builders = new ArrayList<CompilationUnitBuilder>(); |
| |
| // Units we don't want to rebuild unless we have to. |
| Map<CompilationUnitBuilder, CompilationUnit> cachedUnits = new IdentityHashMap<CompilationUnitBuilder, CompilationUnit>(); |
| |
| CompileMoreLater compileMoreLater = new CompileMoreLater(compilerDelegate); |
| |
| // For each incoming Java source file... |
| for (Resource resource : resources) { |
| String typeName = Shared.toTypeName(resource.getPath()); |
| // Create a builder for all incoming units. |
| ResourceCompilationUnitBuilder builder = new ResourceCompilationUnitBuilder( |
| typeName, resource); |
| // Try to get an existing unit from the cache. |
| String location = resource.getLocation(); |
| ResourceTag tag = resourceContentCache.get(location); |
| if (tag != null && tag.getLastModified() == resource.getLastModified()) { |
| ContentId contentId = tag.getContentId(); |
| CompilationUnit existingUnit = unitCache.get(contentId); |
| if (existingUnit != null) { |
| cachedUnits.put(builder, existingUnit); |
| compileMoreLater.addValidUnit(existingUnit); |
| continue; |
| } |
| } |
| builders.add(builder); |
| } |
| Collection<CompilationUnit> resultUnits = compileMoreLater.compile( |
| logger, builders, cachedUnits); |
| return new CompilationState(logger, resultUnits, compileMoreLater); |
| } finally { |
| compilationStateBuilderProcess.end(); |
| } |
| } |
| |
| /** |
| * Compile new generated units into an existing state. |
| * |
| * TODO: maybe use a finer brush than to synchronize the whole thing. |
| */ |
| synchronized Collection<CompilationUnit> doBuildGeneratedTypes( |
| TreeLogger logger, Collection<GeneratedUnit> generatedUnits, |
| CompileMoreLater compileMoreLater) { |
| |
| // Units we definitely want to build. |
| List<CompilationUnitBuilder> builders = new ArrayList<CompilationUnitBuilder>(); |
| |
| // Units we don't want to rebuild unless we have to. |
| Map<CompilationUnitBuilder, CompilationUnit> cachedUnits = new IdentityHashMap<CompilationUnitBuilder, CompilationUnit>(); |
| |
| // For each incoming generated Java source file... |
| for (GeneratedUnit generatedUnit : generatedUnits) { |
| // Create a builder for all incoming units. |
| GeneratedCompilationUnitBuilder builder = new GeneratedCompilationUnitBuilder( |
| generatedUnit); |
| // Try to get an existing unit from the cache. |
| ContentId contentId = new ContentId(generatedUnit.getTypeName(), |
| generatedUnit.getStrongHash()); |
| CompilationUnit existingUnit = unitCache.get(contentId); |
| if (existingUnit != null) { |
| cachedUnits.put(builder, existingUnit); |
| compileMoreLater.addValidUnit(existingUnit); |
| } else { |
| builders.add(builder); |
| } |
| } |
| return compileMoreLater.compile(logger, builders, cachedUnits); |
| } |
| } |