blob: 5cc75c37c221151cff0521e00256afddc62c1f6d [file] [log] [blame]
/*
* Copyright 2007 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.CachedGeneratorResult;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.RebindRuleResolver;
import com.google.gwt.core.ext.SubsetFilteringPropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.impl.ResourceLocatorImpl;
import com.google.gwt.core.ext.linker.Artifact;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.linker.GeneratedResource;
import com.google.gwt.core.ext.linker.impl.StandardGeneratedResource;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.cfg.RuleGenerateWith;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.resource.ResourceOracle;
import com.google.gwt.dev.resource.impl.AbstractResourceOracle;
import com.google.gwt.dev.util.DiskCache;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.collect.HashSet;
import com.google.gwt.dev.util.collect.IdentityHashMap;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.io.Files;
import com.google.gwt.util.tools.Utility;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
/**
* Manages generators and generated units during a single compilation.
*/
public class StandardGeneratorContext implements GeneratorContext {
/**
* Wraps a ResourceOracle to collect the paths of Resources read by Generators.
*/
private class RecordingResourceOracle extends AbstractResourceOracle {
private final ResourceOracle wrappedResourceOracle;
public RecordingResourceOracle(ResourceOracle wrappedResourceOracle) {
this.wrappedResourceOracle = wrappedResourceOracle;
}
@Override
public void clear() {
wrappedResourceOracle.clear();
}
@Override
public Set<String> getPathNames() {
return wrappedResourceOracle.getPathNames();
}
@Override
public Resource getResource(String pathName) {
pathName = Files.simplifyPath(pathName);
compilerContext.getMinimalRebuildCache().associateReboundTypeWithInputResource(
currentRebindBinaryTypeName, pathName);
return wrappedResourceOracle.getResource(pathName);
}
@Override
public Set<Resource> getResources() {
return wrappedResourceOracle.getResources();
}
}
/**
* Extras added to {@link GeneratedUnit}.
*/
private static interface Generated extends GeneratedUnit {
void abort();
void commit(TreeLogger logger);
}
/**
* This generated unit acts as a normal generated unit as well as a buffer
* into which generators can write their source. A controller should ensure
* that source isn't requested until the generator has finished writing it.
* This version is backed by {@link StandardGeneratorContext#diskCache}.
*/
public static class GeneratedUnitImpl implements Generated {
/**
* A token to retrieve this object's bytes from the disk cache.
*/
protected long sourceToken = -1;
private long creationTime;
private String strongHash; // cache so that refreshes work correctly
private StringWriter sw;
private final String typeName;
public GeneratedUnitImpl(StringWriter sw, String typeName) {
this.typeName = typeName;
this.sw = sw;
}
@Override
public void abort() {
sw = null;
}
/**
* Finalizes the source and adds this generated unit to the host.
*/
@Override
public void commit(TreeLogger logger) {
String source = sw.toString();
strongHash = Util.computeStrongName(Util.getBytes(source));
sourceToken = diskCache.writeString(source);
sw = null;
creationTime = System.currentTimeMillis();
}
@Override
public long creationTime() {
return creationTime;
}
@Override
public String getSource() {
if (sw != null) {
throw new IllegalStateException("source not committed");
}
return diskCache.readString(sourceToken);
}
@Override
public String getSourceMapPath() {
return "gen/" + getTypeName().replace('.', '/') + ".java";
}
@Override
public long getSourceToken() {
if (sw != null) {
throw new IllegalStateException("source not committed");
}
return sourceToken;
}
@Override
public String getStrongHash() {
return strongHash;
}
@Override
public String getTypeName() {
return typeName;
}
@Override
public String optionalFileLocation() {
return null;
}
}
/**
* This generated unit acts as a normal generated unit as well as a buffer
* into which generators can write their source. A controller should ensure
* that source isn't requested until the generator has finished writing it.
* This version is backed by an explicit generated file.
*/
private static class GeneratedUnitWithFile extends GeneratedUnitImpl {
private final File file;
public GeneratedUnitWithFile(File file, StringWriter pw, String typeName) {
super(pw, typeName);
this.file = file;
}
@Override
public void commit(TreeLogger logger) {
super.commit(logger);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
diskCache.transferToStream(sourceToken, fos);
} catch (IOException e) {
logger.log(TreeLogger.WARN, "Error writing out generated unit at '"
+ file.getAbsolutePath() + "': " + e);
} finally {
Utility.close(fos);
}
}
@Override
public String optionalFileLocation() {
return file.exists() ? Util.stripJarPathPrefix(file.getAbsolutePath()) : null;
}
}
/**
* Manages a resource that is in the process of being created by a generator.
*/
private static class PendingResource extends OutputStream {
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final String partialPath;
public PendingResource(String partialPath) {
this.partialPath = partialPath;
}
public void abort() {
baos = null;
}
public String getPartialPath() {
return partialPath;
}
public byte[] takeBytes() {
byte[] result = baos.toByteArray();
baos = null;
return result;
}
@Override
public void write(byte[] b) throws IOException {
if (baos == null) {
throw new IOException("stream closed");
}
baos.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (baos == null) {
throw new IOException("stream closed");
}
baos.write(b, off, len);
}
@Override
public void write(int b) throws IOException {
if (baos == null) {
throw new IOException("stream closed");
}
baos.write(b);
}
}
private static final String GENERATOR_VERSION_ID_KEY = "generator-version-id";
private static DiskCache diskCache = DiskCache.INSTANCE;
private static final Map<String, CompilerEventType> eventsByGeneratorType =
new HashMap<String, CompilerEventType>();
static {
eventsByGeneratorType.put(
"com.google.gwt.resources.rebind.context.InlineClientBundleGenerator",
CompilerEventType.GENERATOR_CLIENT_BUNDLE);
eventsByGeneratorType.put("com.google.gwt.i18n.rebind.LocalizableGenerator",
CompilerEventType.GENERATOR_I18N);
eventsByGeneratorType.put("com.google.gwt.i18n.rebind.LocaleInfoGenerator",
CompilerEventType.GENERATOR_I18N);
eventsByGeneratorType.put("com.google.gwt.i18n.rebind.CurrencyListGenerator",
CompilerEventType.GENERATOR_I18N);
eventsByGeneratorType.put("com.google.gwt.i18n.rebind.CustomDateTimeFormatGenerator",
CompilerEventType.GENERATOR_I18N);
eventsByGeneratorType.put("com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator",
CompilerEventType.GENERATOR_RPC);
eventsByGeneratorType.put("com.google.gwt.uibinder.rebind.UiBinderGenerator",
CompilerEventType.GENERATOR_UIBINDER);
eventsByGeneratorType.put("com.google.gwt.inject.rebind.GinjectorGenerator",
CompilerEventType.GENERATOR_GIN);
}
private final ArtifactSet allGeneratedArtifacts;
private final Map<String, GeneratedUnit> committedGeneratedCups =
new HashMap<String, GeneratedUnit>();
private CompilationState compilationState;
private Class<? extends Generator> currentGenerator;
private final File genDir;
private final Map<Class<? extends Generator>, Generator> generators =
new IdentityHashMap<Class<? extends Generator>, Generator>();
private ArtifactSet newlyGeneratedArtifacts = new ArtifactSet();
private final Set<String> newlyGeneratedTypeNames = new HashSet<String>();
private final Map<String, PendingResource> pendingResources =
new HashMap<String, PendingResource>();
private transient PropertyOracle propertyOracle;
private RebindRuleResolver rebindRuleResolver;
private final Map<PrintWriter, Generated> uncommittedGeneratedCupsByPrintWriter =
new IdentityHashMap<PrintWriter, Generated>();
private CachedGeneratorResultImpl cachedRebindResult = null;
private boolean generatorResultCachingEnabled = false;
private List<String> cachedTypeNamesToReuse = null;
private boolean isProdMode;
private CompilerContext compilerContext;
private String currentRebindBinaryTypeName;
private final ResourceOracle buildResourceOracle;
/**
* Normally, the compiler host would be aware of the same types that are
* available in the supplied type oracle although it isn't strictly required.
*/
public StandardGeneratorContext(CompilerContext compilerContext,
CompilationState compilationState, ArtifactSet allGeneratedArtifacts, boolean isProdMode) {
this.compilerContext = compilerContext;
this.compilationState = compilationState;
this.genDir = compilerContext.getOptions().getGenDir();
this.allGeneratedArtifacts = allGeneratedArtifacts;
this.isProdMode = isProdMode;
this.buildResourceOracle =
new RecordingResourceOracle(compilerContext.getBuildResourceOracle());
ResourceLocatorImpl.resetClassLoaderLoadWarningCount();
}
/**
* Adds a generated unit to the context if not already present, but will not
* overwrite an existing unit.
*/
public void addGeneratedUnit(GeneratedUnit gu) {
if (!committedGeneratedCups.containsKey(gu.getTypeName())) {
committedGeneratedCups.put(gu.getTypeName(), gu);
}
}
/**
* Adds generated units to the context, but will not overwrite any existing
* units that might already be present.
*/
public void addGeneratedUnits(Collection<GeneratedUnit> generatedUnits) {
for (GeneratedUnit gu : generatedUnits) {
addGeneratedUnit(gu);
}
}
/**
* Adds all available cached generated units to the context. Existing units
* for a given type will not be overwritten.
*/
public void addGeneratedUnitsFromCache() {
if (cachedRebindResult != null && cachedRebindResult.getGeneratedUnits() != null) {
addGeneratedUnits(cachedRebindResult.getGeneratedUnits());
}
}
/**
* Adds cached generated units to the context that have been marked for reuse.
* Existing units for a given type will not be overwritten.
*/
public void addGeneratedUnitsMarkedForReuseFromCache() {
if (cachedTypeNamesToReuse != null && cachedRebindResult != null) {
for (String typeName : cachedTypeNamesToReuse) {
GeneratedUnit gu = cachedRebindResult.getGeneratedUnit(typeName);
if (gu != null) {
addGeneratedUnit(gu);
}
}
}
}
/**
* Checks whether a rebind rule is available for a given sourceTypeName.
*/
@Override
public boolean checkRebindRuleAvailable(String sourceTypeName) {
if (rebindRuleResolver != null) {
return rebindRuleResolver.checkRebindRuleResolvable(sourceTypeName);
} else {
return false;
}
}
/**
* Frees memory used up by compilation state.
*/
public void clear() {
compilationState = null;
generators.clear();
}
/**
* Commits a pending generated type.
*/
@Override
public final void commit(TreeLogger logger, PrintWriter pw) {
Generated gcup = uncommittedGeneratedCupsByPrintWriter.get(pw);
if (gcup == null) {
logger.log(TreeLogger.WARN, "Generator attempted to commit an unknown PrintWriter", null);
return;
}
gcup.commit(logger);
uncommittedGeneratedCupsByPrintWriter.remove(pw);
committedGeneratedCups.put(gcup.getTypeName(), gcup);
// Write as a source artifact so that a debugger can use it.
// TODO: if we're not generating sourcemaps then we should probably skip this entirely
// since the data will be written to the shard's jar file and never read.
// (But how do we check that?)
if (currentGenerator == null) {
return; // probably a test.
}
GeneratedResource debuggerSource =
new StandardGeneratedResource(gcup.getSourceMapPath(), gcup.getSourceToken());
debuggerSource.setVisibility(Visibility.Source);
commitArtifact(logger, debuggerSource);
}
/**
* Adds an Artifact to the context's ArtifactSets. This will replace a
* pre-existing entry in allGeneratedArtifacts, but will not overwrite an
* entry in the newlyGeneratedArtifacts (since it is assumed by convention
* that only new entries will ever be inserted here for a given generator
* run).
*/
@Override
public void commitArtifact(TreeLogger logger, Artifact<?> artifact) {
allGeneratedArtifacts.replace(artifact);
newlyGeneratedArtifacts.add(artifact);
}
/**
* Commits all available cached Artifacts to the context.
*/
public void commitArtifactsFromCache(TreeLogger logger) {
if (cachedRebindResult != null && cachedRebindResult.getArtifacts() != null) {
for (Artifact<?> art : cachedRebindResult.getArtifacts()) {
commitArtifact(logger, art);
}
}
}
@Override
public GeneratedResource commitResource(TreeLogger logger, OutputStream os)
throws UnableToCompleteException {
PendingResource pendingResource = null;
String partialPath = null;
if (os instanceof PendingResource) {
pendingResource = (PendingResource) os;
partialPath = pendingResource.getPartialPath();
// Make sure it's ours by looking it up in the map.
if (pendingResource != pendingResources.get(partialPath)) {
pendingResource = null;
}
}
if (pendingResource == null) {
logger.log(TreeLogger.WARN, "Generator attempted to commit an unknown OutputStream", null);
throw new UnableToCompleteException();
}
// Add the GeneratedResource to the ArtifactSet
GeneratedResource toReturn =
new StandardGeneratedResource(partialPath, pendingResource.takeBytes());
commitArtifact(logger, toReturn);
pendingResources.remove(pendingResource.getPartialPath());
return toReturn;
}
/**
* Call this whenever generators are known to not be running to clear out
* uncommitted compilation units and to force committed compilation units to
* be parsed and added to the type oracle.
*
* @return any newly generated artifacts since the last call
*
* @throw UnableToCompleteException if the compiler aborted (not
* a normal compile error).</p>
*/
public ArtifactSet finish(TreeLogger logger) throws UnableToCompleteException {
abortUncommittedResources(logger);
try {
TreeLogger branch;
if (isDirty()) {
// Assimilate the new types into the type oracle.
//
String msg = "Assimilating generated source";
branch = logger.branch(TreeLogger.DEBUG, msg, null);
TreeLogger subBranch = null;
if (branch.isLoggable(TreeLogger.DEBUG)) {
subBranch = branch.branch(TreeLogger.DEBUG, "Generated source files...", null);
}
for (GeneratedUnit gcup : committedGeneratedCups.values()) {
String qualifiedTypeName = gcup.getTypeName();
if (subBranch != null) {
subBranch.log(TreeLogger.DEBUG, qualifiedTypeName, null);
}
}
compilationState.addGeneratedCompilationUnits(logger, committedGeneratedCups.values());
}
return newlyGeneratedArtifacts;
} finally {
// Remind the user if there uncommitted cups.
if (!uncommittedGeneratedCupsByPrintWriter.isEmpty()) {
String msg =
"For the following type(s), generated source was never committed (did you forget to call commit()?)";
logger = logger.branch(TreeLogger.WARN, msg, null);
for (Generated unit : uncommittedGeneratedCupsByPrintWriter.values()) {
logger.log(TreeLogger.WARN, unit.getTypeName(), null);
}
}
reset();
}
}
public boolean isDirty() {
return !committedGeneratedCups.isEmpty();
}
/**
* Clears all accumulated artifacts and state so that the context can be used
* as if from scratch. Is useful for clearing out undesired changes after
* having used the context to explore some hypothetical situations, for
* example to run Generators for the purpose of discovering the properties
* they depend on.
*/
public void reset() {
uncommittedGeneratedCupsByPrintWriter.clear();
committedGeneratedCups.clear();
newlyGeneratedTypeNames.clear();
newlyGeneratedArtifacts = new ArtifactSet();
cachedTypeNamesToReuse = null;
}
public Set<String> getActiveLinkerNames() {
return compilerContext.getModule().getActiveLinkerNames();
}
/**
* Gets newly committed artifacts.
*/
public ArtifactSet getArtifacts() {
return new ArtifactSet(newlyGeneratedArtifacts);
}
/**
* Gets the previously cached rebind result for the current generator.
*/
@Override
public CachedGeneratorResult getCachedGeneratorResult() {
return cachedRebindResult;
}
public GeneratorContext getCanonicalContext() {
return this;
}
public CompilationState getCompilationState() {
return compilationState;
}
/**
* Gets all committed Java units.
*/
public Map<String, GeneratedUnit> getGeneratedUnitMap() {
return committedGeneratedCups;
}
@Override
public final PropertyOracle getPropertyOracle() {
return propertyOracle;
}
@Override
public ResourceOracle getResourcesOracle() {
return buildResourceOracle;
}
@Override
public final TypeOracle getTypeOracle() {
return compilationState.getTypeOracle();
}
@Override
public boolean isGeneratorResultCachingEnabled() {
return generatorResultCachingEnabled;
}
@Override
public boolean isProdMode() {
return isProdMode;
}
/**
* This method is maintained for backwards compatibility.
* {@link #runGeneratorIncrementally} should be used instead.
*/
public String runGenerator(TreeLogger logger, Class<? extends Generator> generatorClass,
String typeName) throws UnableToCompleteException {
RebindResult result = runGeneratorIncrementally(logger, generatorClass, typeName);
return result.getResultTypeName();
}
/**
* Runs a generator incrementally, with support for managing the returned
* {@link RebindResult} object, which can contain cached results. This is a
* replacement for the {@link #runGenerator} method.
* <p>
* If the passed in generatorClass is an instance of
* {@link IncrementalGenerator}, it's
* {@link IncrementalGenerator#generateIncrementally} method will be called.
* <p>
* Otherwise, for backwards compatibility, the generatorClass will be wrapped
* in a {@link IncrementalGenerator} instance, and it's
* {@link Generator#generate} method will be called.
*
* @param logger
* @param generatorClass
* @param typeName
* @return a RebindResult
* @throws UnableToCompleteException
*/
public RebindResult runGeneratorIncrementally(TreeLogger logger,
Class<? extends Generator> generatorClass, String typeName) throws UnableToCompleteException {
String msg = "Invoking generator " + generatorClass.getName();
logger = logger.branch(TreeLogger.DEBUG, msg, null);
Generator generator = generators.get(generatorClass);
if (generator == null) {
try {
generator = generatorClass.newInstance();
generators.put(generatorClass, generator);
} catch (Throwable e) {
logger.log(TreeLogger.ERROR, "Unexpected error trying to instantiate Generator '"
+ generatorClass.getName() + "'", e);
throw new UnableToCompleteException();
}
}
setCurrentGenerator(generatorClass);
// Avoid call to System.currentTimeMillis() if not logging DEBUG level
boolean loggable = logger.isLoggable(TreeLogger.DEBUG);
long before = loggable ? System.currentTimeMillis() : 0L;
String generatorClassName = generator.getClass().getName();
CompilerEventType type = eventsByGeneratorType.get(generatorClassName);
if (type == null) {
type = CompilerEventType.GENERATOR_OTHER;
}
Event generatorEvent =
SpeedTracerLogger.start(type, "class", generatorClassName, "type", typeName);
PropertyOracle originalPropertyOracle = propertyOracle;
try {
RebindResult result;
// TODO(stalcup): refactor the Generator/PropertyOracle system (in a potentially backwards
// incompatible way) so that all Generators are forced to accurately declare the names of
// properties they care about.
propertyOracle = new SubsetFilteringPropertyOracle(
RuleGenerateWith.getAccessedPropertyNames(generator.getClass()), originalPropertyOracle,
generatorClassName + "'s RunsLocal annotation may need to be updated.");
if (generator instanceof IncrementalGenerator) {
IncrementalGenerator incGenerator = (IncrementalGenerator) generator;
// check version id for any previously cached rebind result
if (cachedRebindResult != null) {
Long cachedVersionId = (Long) cachedRebindResult.getClientData(GENERATOR_VERSION_ID_KEY);
if (cachedVersionId != null && cachedVersionId != incGenerator.getVersionId()) {
// remove from context
if (logger.isLoggable(TreeLogger.TRACE)) {
logger.log(TreeLogger.TRACE, "Got version mismatch with cached generator result for "
+ typeName + ", invalidating cached result");
}
cachedRebindResult = null;
}
}
// run the generator
result = incGenerator.generateIncrementally(logger, this, typeName);
// add version id to the returned result
result.putClientData(GENERATOR_VERSION_ID_KEY, incGenerator.getVersionId());
} else {
// run a non-incremental generator
result = IncrementalGenerator.generateNonIncrementally(logger, generator, this, typeName);
}
if (loggable) {
long after = System.currentTimeMillis();
msg =
"Generator returned type '" + result.getResultTypeName() + "; mode "
+ result.getRebindMode() + "; in " + (after - before) + " ms";
logger.log(TreeLogger.DEBUG, msg, null);
}
return result;
} catch (AssertionError e) {
// Catch and log the assertion as a convenience to the developer
logger.log(TreeLogger.ERROR, "Generator '" + generatorClass.getName()
+ "' failed an assertion while rebinding '" + typeName + "'", e);
throw new UnableToCompleteException();
} catch (RuntimeException e) {
logger.log(TreeLogger.ERROR, "Generator '" + generatorClass.getName()
+ "' threw an exception while rebinding '" + typeName + "'", e);
throw new UnableToCompleteException();
} finally {
propertyOracle = originalPropertyOracle;
generatorEvent.end();
}
}
/**
* Set previously cached rebind result for currently active generator.
*/
public void setCachedGeneratorResult(CachedGeneratorResult cachedRebindResult) {
this.cachedRebindResult = (CachedGeneratorResultImpl) cachedRebindResult;
}
public void setCurrentGenerator(Class<? extends Generator> currentGenerator) {
this.currentGenerator = currentGenerator;
}
public void setCurrentRebindBinaryTypeName(String currentRebindBinaryTypeName) {
this.currentRebindBinaryTypeName = currentRebindBinaryTypeName;
}
public void setGeneratorResultCachingEnabled(boolean enabled) {
this.generatorResultCachingEnabled = enabled;
}
/**
* Sets the current transient property oracle to answer current property
* questions.
*/
public void setPropertyOracle(PropertyOracle propertyOracle) {
this.propertyOracle = propertyOracle;
}
public void setRebindRuleResolver(RebindRuleResolver resolver) {
this.rebindRuleResolver = resolver;
}
@Override
public PrintWriter tryCreate(TreeLogger logger, String packageName, String simpleTypeName) {
String typeName;
if (packageName.length() == 0) {
typeName = simpleTypeName;
} else {
typeName = packageName + '.' + simpleTypeName;
}
compilerContext.getMinimalRebuildCache().associateReboundTypeWithGeneratedCompilationUnitName(
currentRebindBinaryTypeName, typeName);
// Is type already known to the host?
JClassType existingType = getTypeOracle().findType(packageName, simpleTypeName);
if (existingType != null) {
if (logger.isLoggable(TreeLogger.DEBUG)) {
logger.log(TreeLogger.DEBUG, "Type '" + typeName
+ "' already exists and will not be re-created ", null);
}
return null;
}
// Type recently generated?
if (newlyGeneratedTypeNames.contains(typeName)) {
return null;
}
// The type isn't there, so we can let the caller create it. Remember that
// it is pending so another attempt to create the same type will fail.
Generated gcup;
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true) {
/**
* Overridden to force unix-style line endings for consistent behavior
* across platforms.
*/
@Override
public void println() {
super.print('\n');
super.flush();
}
};
if (this.genDir == null) {
gcup = new GeneratedUnitImpl(sw, typeName);
} else {
File dir = new File(genDir, packageName.replace('.', File.separatorChar));
// No need to check mkdirs result because an IOException will occur anyway
dir.mkdirs();
File srcFile = new File(dir, simpleTypeName + ".java");
if (srcFile.exists()) {
srcFile.delete();
}
gcup = new GeneratedUnitWithFile(srcFile, sw, typeName);
}
uncommittedGeneratedCupsByPrintWriter.put(pw, gcup);
newlyGeneratedTypeNames.add(typeName);
return pw;
}
@Override
public OutputStream tryCreateResource(TreeLogger logger, String partialPath)
throws UnableToCompleteException {
logger =
logger.branch(TreeLogger.DEBUG, "Preparing pending output resource '" + partialPath + "'",
null);
// Disallow null or empty names.
if (partialPath == null || partialPath.trim().equals("")) {
logger.log(TreeLogger.ERROR, "The resource name must be a non-empty string", null);
throw new UnableToCompleteException();
}
// Disallow absolute paths.
if (new File(partialPath).isAbsolute()) {
logger
.log(
TreeLogger.ERROR,
"Resource paths are intended to be relative to the compiled output directory and cannot be absolute",
null);
throw new UnableToCompleteException();
}
// Disallow backslashes (to promote consistency in calling code).
if (partialPath.indexOf('\\') >= 0) {
logger.log(TreeLogger.ERROR,
"Resource paths must contain forward slashes (not backslashes) to denote subdirectories",
null);
throw new UnableToCompleteException();
}
// Check for public path collision.
if (compilerContext.getPublicResourceOracle().getResource(partialPath) != null) {
logger.log(TreeLogger.WARN, "Cannot create resource '" + partialPath
+ "' because it already exists on the public path", null);
return null;
}
// See if the file is already committed.
SortedSet<GeneratedResource> resources = allGeneratedArtifacts.find(GeneratedResource.class);
for (GeneratedResource resource : resources) {
if (partialPath.equals(resource.getPartialPath())) {
return null;
}
}
// See if the file is pending.
if (pendingResources.containsKey(partialPath)) {
// It is already pending.
logger.log(TreeLogger.WARN, "The file '" + partialPath + "' is already a pending resource",
null);
return null;
}
PendingResource pendingResource = new PendingResource(partialPath);
pendingResources.put(partialPath, pendingResource);
return pendingResource;
}
/**
* Adds a type name to the list of types to be reused from cache, if
* available.
*
* @param typeName The fully qualified name of a type.
*
* @return true, if the type is available in the cache and was successfully
* added to the list for reuse, false otherwise.
*/
@Override
public boolean tryReuseTypeFromCache(String typeName) {
if (!isGeneratorResultCachingEnabled() || cachedRebindResult == null
|| !cachedRebindResult.isTypeCached(typeName)) {
return false;
}
if (cachedTypeNamesToReuse == null) {
cachedTypeNamesToReuse = new ArrayList<String>();
}
cachedTypeNamesToReuse.add(typeName);
return true;
}
private void abortUncommittedResources(TreeLogger logger) {
if (pendingResources.isEmpty()) {
// Nothing to do.
return;
}
// Warn the user about uncommitted resources.
logger = logger.branch(TreeLogger.WARN,
"The following resources will not be created because they were never "
+ "committed (did you forget to call commit()?)", null);
for (Entry<String, PendingResource> entry : pendingResources.entrySet()) {
logger.log(TreeLogger.WARN, entry.getKey());
entry.getValue().abort();
}
pendingResources.clear();
}
}