blob: 09dfd61338a43b22d71e886dfd42bcf2c3ea8b41 [file] [log] [blame]
/*
* 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);
}
}