Java tutorial
/******************************************************************************* * Copyright 2011 Google Inc. All Rights Reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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 java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; 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.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; 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.CompilationUnitBuilder.CachedResourceCompilationUnitBuilder; import com.google.gwt.dev.javac.JdtCompiler.UnitProcessor; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.resource.Resource; /** * 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) { String fileName = String.valueOf(resolveType.getFileName()); jsniDeps.add(fileName); } return resolveType; } }); JSORestrictionsChecker.check(jsoState, cud); ArtificialRescueChecker.check(cud, builder.isGenerated()); BinaryTypeReferenceRestrictionsChecker.check(cud); MethodArgNamesLookup methodArgs = MethodParamCollector.collect(cud); CompilationUnitInvalidator.reportErrors(logger, cud, builder.getSource()); Set<ContentId> dependencies = compiler.computeDependencies(cud, jsniDeps); CompilationUnit unit = builder.build(compiledClasses, dependencies, jsniMethods.values(), methodArgs, cud.compilationResult().getProblems()); // XXX Instantiations if (false && cud.compilationResult().hasErrors()) { unit = new ErrorCompilationUnit(unit); } else { 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); } } resultUnits.put(unit.getTypeName(), unit); } } /** * 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 TreeLogger logger; private transient Map<String, CompilationUnit> resultUnits; private final Set<ContentId> validDependencies = new HashSet<ContentId>(); public Collection<CompilationUnit> addGeneratedTypes(TreeLogger logger, Collection<GeneratedUnit> generatedUnits) { return doBuildGeneratedTypes(logger, generatedUnits, this); } void addValidUnit(CompilationUnit unit) { assert unit.isCompiled(); compiler.addCompiledUnit(unit); validDependencies.add(unit.getContentId()); } void compile(TreeLogger logger, Collection<CompilationUnitBuilder> builders, Map<String, CompilationUnit> resultUnits) { this.logger = logger.branch(TreeLogger.DEBUG, "Validating newly compiled units"); this.resultUnits = resultUnits; compiler.doCompile(builders); } Set<ContentId> getValidDependencies() { return validDependencies; } } /** * 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) { return instance.doBuildFrom(logger, resources); } public static CompilationStateBuilder get() { return instance; } private static void invalidateUnitsWithInvalidRefs(TreeLogger logger, Map<String, CompilationUnit> resultUnits, Set<ContentId> set) { Set<CompilationUnit> validResultUnits = new HashSet<CompilationUnit>(resultUnits.values()); CompilationUnitInvalidator.retainValidUnits(logger, validResultUnits, set); for (Entry<String, CompilationUnit> entry : resultUnits.entrySet()) { CompilationUnit unit = entry.getValue(); if (unit.isCompiled() && !validResultUnits.contains(unit)) { entry.setValue(new InvalidCompilationUnit(unit)); } } } /** * 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. */ @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. */ @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. * * TODO: maybe use a finer brush than to synchronize the whole thing. */ public synchronized CompilationState doBuildFrom(TreeLogger logger, Set<Resource> resources) { Map<String, CompilationUnit> resultUnits = new HashMap<String, CompilationUnit>(); CompileMoreLater compileMoreLater = new CompileMoreLater(); // For each incoming Java source file... for (Resource resource : resources) { // 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 && existingUnit.isCompiled()) { resultUnits.put(existingUnit.getTypeName(), existingUnit); continue; } } // try to get from the binary cache CompilationUnit unit = CompilationUnitDiskCache.get().get(resource, jsProgram); if (unit != null) { resultUnits.put(unit.getTypeName(), unit); } } // Winnow the reusable set of units down to those still valid. CompilationUnitInvalidator.retainValidUnits(TreeLogger.NULL, resultUnits.values()); // Compile everything else. List<CompilationUnitBuilder> builders = new ArrayList<CompilationUnitBuilder>(); for (Resource resource : resources) { String typeName = Shared.toTypeName(resource.getPath()); // >>> XXX Instantiations: Eclipse 3.5 compiler doesn't like 'package-info.java' CUs, so exclude them if (typeName.endsWith("package-info")) { continue; } // <<< XXX Instantiations CompilationUnit validUnit = resultUnits.get(typeName); if (validUnit != null) { compileMoreLater.addValidUnit(validUnit); // Report any existing errors as if the unit were recompiled. CompilationUnitInvalidator.reportErrors(logger, validUnit); } else { // >>> XXX Instantiations builders.add(new CachedResourceCompilationUnitBuilder(typeName, resource)); // <<< XXX Instantiations } } compileMoreLater.compile(logger, builders, resultUnits); // Invalidate units with invalid refs. invalidateUnitsWithInvalidRefs(logger, resultUnits, Collections.<ContentId>emptySet()); return new CompilationState(logger, resultUnits.values(), compileMoreLater); } /** * 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) { Map<String, CompilationUnit> resultUnits = new HashMap<String, CompilationUnit>(); // For each incoming generated Java source file... for (GeneratedUnit generatedUnit : generatedUnits) { // 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 && existingUnit.isCompiled()) { resultUnits.put(existingUnit.getTypeName(), existingUnit); } } // Winnow the reusable set of units down to those still valid. CompilationUnitInvalidator.retainValidUnits(TreeLogger.NULL, resultUnits.values(), compileMoreLater.getValidDependencies()); for (CompilationUnit validUnit : resultUnits.values()) { compileMoreLater.addValidUnit(validUnit); // Report any existing errors as if the unit were recompiled. CompilationUnitInvalidator.reportErrors(logger, validUnit); } // Compile everything else. List<CompilationUnitBuilder> builders = new ArrayList<CompilationUnitBuilder>(); for (GeneratedUnit generatedUnit : generatedUnits) { if (!resultUnits.containsKey(generatedUnit.getTypeName())) { builders.add(new GeneratedCompilationUnitBuilder(generatedUnit)); } } compileMoreLater.compile(logger, builders, resultUnits); invalidateUnitsWithInvalidRefs(logger, resultUnits, compileMoreLater.getValidDependencies()); return resultUnits.values(); } }