blob: 8449fee75c3e257c6486f5cfc0469434193ad595 [file] [log] [blame]
/*
* Copyright 2008 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.shell;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.dev.cfg.Rule;
import com.google.gwt.dev.cfg.Rules;
import com.google.gwt.dev.javac.StandardGeneratorContext;
import com.google.gwt.dev.javac.rebind.CachedRebindResult;
import com.google.gwt.dev.javac.rebind.RebindCache;
import com.google.gwt.dev.javac.rebind.RebindResult;
import com.google.gwt.dev.javac.rebind.RebindRuleResolver;
import com.google.gwt.dev.javac.rebind.RebindStatus;
import com.google.gwt.dev.jdt.RebindOracle;
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 java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Implements rebind logic in terms of a variety of other well-known oracles.
*/
public class StandardRebindOracle implements RebindOracle {
/**
* Makes the actual deferred binding decision by examining rules.
*/
private final class Rebinder implements RebindRuleResolver {
@Override
public boolean checkRebindRuleResolvable(String typeName) {
try {
if (getRebindRule(TreeLogger.NULL, typeName) != null) {
return true;
}
} catch (UnableToCompleteException utcEx) {
}
return false;
}
public String rebind(TreeLogger logger, String typeName, ArtifactAcceptor artifactAcceptor)
throws UnableToCompleteException {
Event rebindEvent = SpeedTracerLogger.start(DevModeEventType.REBIND, "Type Name", typeName);
try {
genCtx.setPropertyOracle(propOracle);
genCtx.setRebindRuleResolver(this);
Rule rule = getRebindRule(logger, typeName);
if (rule == null) {
return typeName;
}
CachedRebindResult cachedResult = rebindCacheGet(rule, typeName);
if (cachedResult != null) {
genCtx.setCachedGeneratorResult(cachedResult);
}
// realize the rule (call a generator, or do type replacement, etc.)
RebindResult result = rule.realize(logger, genCtx, typeName);
// handle rebind result caching (if enabled)
String resultTypeName =
processCacheableResult(logger, rule, typeName, cachedResult, result);
/*
* Finalize new artifacts from the generator context
*/
if (artifactAcceptor != null) {
// Go ahead and call finish() to accept new artifacts.
ArtifactSet newlyGeneratedArtifacts = genCtx.finish(logger);
if (!newlyGeneratedArtifacts.isEmpty()) {
artifactAcceptor.accept(logger, newlyGeneratedArtifacts);
}
}
assert (resultTypeName != null);
return resultTypeName;
} finally {
rebindEvent.end();
}
}
private Rule getRebindRule(TreeLogger logger, String typeName) throws UnableToCompleteException {
// Make the rebind decision.
//
if (rules.isEmpty()) {
logger.log(TreeLogger.DEBUG, "No rules are defined, so no substitution can occur", null);
return null;
}
Rule minCostRuleSoFar = null;
for (Iterator<Rule> iter = rules.iterator(); iter.hasNext();) {
Rule rule = iter.next();
// Branch the logger.
//
TreeLogger branch = Messages.TRACE_CHECKING_RULE.branch(logger, rule, null);
if (rule.isApplicable(branch, genCtx, typeName)) {
Messages.TRACE_RULE_MATCHED.log(logger, null);
return rule;
} else {
Messages.TRACE_RULE_DID_NOT_MATCH.log(logger, null);
// keep track of fallback partial matches
if (minCostRuleSoFar == null) {
minCostRuleSoFar = rule;
}
assert rule.getFallbackEvaluationCost() != 0;
// if we found a better match, keep that as the best candidate so far
if (rule.getFallbackEvaluationCost() <= minCostRuleSoFar.getFallbackEvaluationCost()) {
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG, "Found better fallback match for " + rule);
}
minCostRuleSoFar = rule;
}
}
}
// if we reach this point, it means we did not find an exact match
// and we may have a partial match based on fall back values
assert minCostRuleSoFar != null;
if (minCostRuleSoFar.getFallbackEvaluationCost() < Integer.MAX_VALUE) {
if (logger.isLoggable(TreeLogger.INFO)) {
logger.log(TreeLogger.INFO, "Could not find an exact match rule. Using 'closest' rule "
+ minCostRuleSoFar
+ " based on fall back values. You may need to implement a specific "
+ "binding in case the fall back behavior does not replace the missing binding");
}
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG, "No exact match was found, using closest match rule "
+ minCostRuleSoFar);
}
return minCostRuleSoFar;
}
// No matching rule for this type.
return null;
}
/*
* Decide how to handle integrating a previously cached result, and whether
* to cache the new result for the future.
*/
private String processCacheableResult(TreeLogger logger, Rule rule, String typeName,
CachedRebindResult cachedResult, RebindResult newResult) {
String resultTypeName = newResult.getReturnedTypeName();
if (!genCtx.isGeneratorResultCachingEnabled()) {
return resultTypeName;
}
RebindStatus status = newResult.getResultStatus();
switch (status) {
case USE_EXISTING:
// in this case, no newly generated or cached types are needed
break;
case USE_ALL_NEW_WITH_NO_CACHING:
/*
* in this case, new artifacts have been generated, but no need to
* cache results (as the generator is probably not able to take
* advantage of caching).
*/
break;
case USE_ALL_NEW:
// use all new results, add a new cache entry
cachedResult =
new CachedRebindResult(newResult.getReturnedTypeName(), genCtx.getArtifacts(), genCtx
.getGeneratedUnitMap(), System.currentTimeMillis(), newResult.getClientDataMap());
rebindCachePut(rule, typeName, cachedResult);
break;
case USE_ALL_CACHED:
// use all cached results
assert (cachedResult != null);
genCtx.commitArtifactsFromCache(logger);
genCtx.addGeneratedUnitsFromCache();
// use cached type name
resultTypeName = cachedResult.getReturnedTypeName();
break;
case USE_PARTIAL_CACHED:
/*
* Add cached generated units marked for reuse to the context.
* TODO(jbrosenberg): add support for reusing artifacts as well as
* GeneratedUnits.
*/
genCtx.addGeneratedUnitsMarkedForReuseFromCache();
/*
* Create a new cache entry using the composite set of new and reused
* cached results currently in genCtx.
*/
cachedResult =
new CachedRebindResult(newResult.getReturnedTypeName(), genCtx.getArtifacts(), genCtx
.getGeneratedUnitMap(), System.currentTimeMillis(), newResult.getClientDataMap());
rebindCachePut(rule, typeName, cachedResult);
break;
}
return resultTypeName;
}
}
private final Map<String, String> typeNameBindingMap = new HashMap<String, String>();
private final StandardGeneratorContext genCtx;
private final PropertyOracle propOracle;
private RebindCache rebindCache = null;
private final Rules rules;
public StandardRebindOracle(PropertyOracle propOracle, Rules rules,
StandardGeneratorContext genCtx) {
this.propOracle = propOracle;
this.rules = rules;
this.genCtx = genCtx;
}
/**
* Invalidates the given source type name, so the next rebind request will
* generate the type again.
*/
public void invalidateRebind(String sourceTypeName) {
typeNameBindingMap.remove(sourceTypeName);
}
public String rebind(TreeLogger logger, String typeName) throws UnableToCompleteException {
return rebind(logger, typeName, null);
}
public String rebind(TreeLogger logger, String typeName, ArtifactAcceptor artifactAcceptor)
throws UnableToCompleteException {
String resultTypeName = typeNameBindingMap.get(typeName);
if (resultTypeName == null) {
logger = Messages.TRACE_TOPLEVEL_REBIND.branch(logger, typeName, null);
Rebinder rebinder = new Rebinder();
resultTypeName = rebinder.rebind(logger, typeName, artifactAcceptor);
typeNameBindingMap.put(typeName, resultTypeName);
Messages.TRACE_TOPLEVEL_REBIND_RESULT.log(logger, resultTypeName, null);
}
return resultTypeName;
}
public void setRebindCache(RebindCache cache) {
this.rebindCache = cache;
}
private CachedRebindResult rebindCacheGet(Rule rule, String typeName) {
if (rebindCache != null) {
return rebindCache.get(rule, typeName);
}
return null;
}
private void rebindCachePut(Rule rule, String typeName, CachedRebindResult result) {
if (rebindCache != null) {
rebindCache.put(rule, typeName, result);
}
}
}