public static int validateCompilationUnits(FileSpec fileSpec, SourceList sourceList, SourcePath sourcePath,
ResourceBundlePath bundlePath, ResourceContainer resources,
CompilerSwcContext swcContext, Map<String, Source> includedClasses,
ContextStatics perCompileData, Configuration configuration)
{
final LocalizationManager l10n = ThreadLocalToolkit.getLocalizationManager();
final boolean strict = configuration.getCompilerConfiguration().strict();
final Map<String, Source>
updated = new HashMap<String, Source>(), // VirtualFile.getName() -> Source
updatedWithStableSignature = new HashMap<String, Source>(), // VirtualFile.getName() -> Source
affected = new HashMap<String, Source>(); // VirtualFile.getName() -> Source
final Map<QName, Source> deleted = new HashMap<QName, Source>();
final Map<String, String> reasons = new HashMap<String, String>(); // VirtualFile.getName() -> String
final Map<QName, Source> qNames = new HashMap<QName, Source>();
final Set<String> includeUpdated = new HashSet<String>(), // VirtualFile.getName()
resourceDelegates = new HashSet<String>(), // VirtualFile.getNameForReporting()
namespaces = new HashSet<String>();
final Map<QName, Map<String, Source>> dependents = new HashMap<QName, Map<String, Source>>();
Set<Source> swcSources = swcContext.cachedSources();
Context ascContext = null;
if (perCompileData != null)
{
ascContext = new Context(perCompileData);
}
// put all the Source objects together
final Set<Source> sources = new HashSet<Source>();
{
sources.addAll(swcSources);
if (fileSpec != null)
sources.addAll(fileSpec.sources());
if (sourceList != null)
sources.addAll(sourceList.sources().values());
if (sourcePath != null)
sources.addAll(sourcePath.sources().values());
if (bundlePath != null)
sources.addAll(bundlePath.sources().values());
if (includedClasses != null)
sources.addAll(includedClasses.values());
}
// build a dependency graph
for (Source source : sources)
{
if (source.getName() == null)
{
continue;
}
CompilationUnit u = source.getCompilationUnit();
if (u == null)
{
continue;
}
// collect the names of all the update file includes...
for (Iterator j = source.getUpdatedFileIncludes(); j != null && j.hasNext();)
{
VirtualFile f = (VirtualFile) j.next();
includeUpdated.add(f.getNameForReporting());
}
// register QName --> VirtualFile.getName()
for (QName qName : u.topLevelDefinitions)
{
qNames.put(qName, source);
dependents.put(qName, new HashMap<String, Source>());
}
}
for (Source source : resources.sources().values())
{
if (source.getName() == null)
{
continue;
}
CompilationUnit u = source.getCompilationUnit();
if (u == null)
{
continue;
}
// register QName --> VirtualFile.getName()
for (QName qName : u.topLevelDefinitions)
{
qNames.put(qName, source);
}
}
// setup inheritance-based dependencies...
for (Source source : sources)
{
if (source == null) continue;
CompilationUnit u = source.getCompilationUnit();
if (u == null) continue;
addDependents(source, u.inheritance, dependents);
addDependents(source, u.namespaces, dependents);
addDependents(source, u.types, dependents);
addDependents(source, u.expressions, dependents);
}
Logger logger = ThreadLocalToolkit.getLogger();
// if any of the Source objects in ResourceContainer is bad, obsolete the originating Source.
for (Source source : resources.sources().values())
{
CompilationUnit u = source.getCompilationUnit();
if (source.hasError() ||
(u != null && !u.isDone() && !u.hasTypeInfo) ||
source.isUpdated() ||
(u != null && u.hasAssets() && u.getAssets().isUpdated()))
{
resourceDelegates.add(source.getNameForReporting());
source.removeCompilationUnit();
}
}
reportObsoletedSwcSources(swcContext, l10n, logger);
reportShadowedSwcSources(swcSources, sourceList, sourcePath, resources, l10n, logger, sources);
// identify obsolete CompilationUnit
// - NotFullyCompiled
// - SourceNoLongerExists
// - SourceFileUpdated
// - AssedUpdated
for (Iterator<Source> iterator = sources.iterator(); iterator.hasNext();)
{
Source s = iterator.next();
CompilationUnit u = s.getCompilationUnit();
// Sources for internal classes like Object never reach the done state or have typeInfo.
if (s.hasError() ||
(!s.isInternal() && (u != null && !u.isDone() && !u.hasTypeInfo)) ||
resourceDelegates.contains(s.getName()))
{
affected.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new NotFullyCompiled()));
iterator.remove();
}
else if (!s.exists())
{
updated.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new SourceNoLongerExists()));
if (u != null)
{
for (QName qName : u.topLevelDefinitions)
{
namespaces.add(qName.toString());
deleted.put(qName, s);
}
}
iterator.remove();
}
else if (s.isUpdated())
{
// signature optimization:
// read the old signature from the incremental cache
// generate a new signature from the current source
// compare -- if stable, we don't have to recompile dependencies
boolean signatureIsStable = false;
if ((u != null) &&
(!configuration.getCompilerConfiguration().getDisableIncrementalOptimizations()) &&
// skip MXML sources:
// MXML is too complicated to parse/codegen at this point in
// order to generate and compare a new checksum
(!s.getMimeType().equals(MimeMappings.MXML)))
{
final Long persistedCRC = u.getSignatureChecksum();
if (persistedCRC != null)
{
assert (s.getMimeType().equals(MimeMappings.ABC) ||
s.getMimeType().equals(MimeMappings.AS));
//TODO if we calculate a new checksum that does not match,
// can we store this checksum and not recompute it later?
final Long currentCRC = computeSignatureChecksum(configuration, s);
signatureIsStable = (currentCRC != null) &&
(persistedCRC.compareTo(currentCRC) == 0);
// if (SignatureExtension.debug)
// {
// final String name = u.getSource().getName();
// SignatureExtension.debug("*** FILE UPDATED: Signature "
// + (signatureIsStable ? "IS" : "IS NOT")
// + " stable ***");
// SignatureExtension.debug("PERSISTED CRC32: " + persistedCRC + "\t--> " + name);
// SignatureExtension.debug("CURRENT CRC32: " + currentCRC + "\t--> " + name);
// }
}
}
// if the class signature is stable (it has not changed since the last compile)
// then we can invalidate and recompile the updated unit alone
// otherwise we default to a chain reaction, invalidating _all_ dependent units
if (signatureIsStable)
{
updatedWithStableSignature.put(s.getName(), s);
}
else
{
updated.put(s.getName(), s);
}
reasons.put(s.getName(), l10n.getLocalizedTextString(new SourceFileUpdated()));
iterator.remove();
}
else if (u != null && u.hasAssets() && u.getAssets().isUpdated())
{
updated.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new AssetUpdated()));
iterator.remove();
}
}
// permanently remove the deleted Source objects from SourcePath
//
// Note: this step is currently necessary because the location-updating loop that follows iterates over
// 'sources', which has had deleted entries remove. So here we iterate directly over the deleted
// entries. (Note also that 'reasons' already has an entry for this source.)
//
for (Source source : deleted.values())
{
if (source.isSourcePathOwner())
{
SourcePath sp = (SourcePath) source.getOwner();
sp.removeSource(source);
if (ascContext != null)
{
CompilationUnit u = source.getCompilationUnit();
if (u != null)
{
for (QName defName : u.topLevelDefinitions)
{
ascContext.removeUserDefined(defName.toString());
}
}
}
}
}
// Examine each Source object in SourcePath or ResourceBundlePath...
// if a Source object in SourcePath or ResourceBundlePath is no longer the
// first choice according to findFile, it should be removed... i.e. look for ambiguous sources
// - NotSourcePathFirstPreference
for (Iterator<Source> iterator = sources.iterator(); iterator.hasNext();)
{
Source s = iterator.next();
if (s.isSourcePathOwner() || s.isResourceBundlePathOwner())
{
SourcePathBase sp = (SourcePathBase) s.getOwner();
if (!sp.checkPreference(s))
{
affected.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new NotSourcePathFirstPreference()));
iterator.remove();
}
}
}
// invalidate the compilation unit if its dependencies are updated or not cached.
// - DependencyUpdated
// - DependencyNotCached
// - InvalidImportStatement
for (Iterator<Source> iterator = sources.iterator(); iterator.hasNext();)
{
Source s = iterator.next();
CompilationUnit u = s.getCompilationUnit();
if (u == null) continue;
Set<Name> dependencies = new HashSet<Name>();
dependencies.addAll(u.inheritance);
dependencies.addAll(u.namespaces);
dependencies.addAll(u.expressions);
dependencies.addAll(u.types);
// Every CompilationUnit has "Object" at the top of it's
// inheritance chain. As a result, in
// As3Compiler.analyze2(), we call inheritSlots() on
// Object's frame, which unfortunately includes lots of
// other builtins, like String, Number, Namespace, etc.
// By inheriting slots for these other builtins, they are
// not reported as unresolved, so they are not recorded as
// dependencies. When switching between airglobal.swc and
// playerglobal.swc in the same workspace, the builtins
// change, so we need to check for that. We use
// "Namespace" to represent the set of builtins. See
// SDK-25206.
dependencies.add(new QName("", "Namespace"));
boolean valid = true;
for (Name dependentName : dependencies)
{
QName qName = toQName(dependentName);
if (qName != null)
{
Source dependentSource = qNames.get(qName);
if (dependentSource != null)
{
CompilationUnit dependentCompilationUnit = dependentSource.getCompilationUnit();
if (u.hasTypeInfo && !dependentCompilationUnit.hasTypeInfo && !dependentSource.isInternal())
{
reasons.put(s.getName(), l10n.getLocalizedTextString(new DependencyNotCached(dependentName.toString())));
valid = false;
}
else
{
// If the dependency hasn't been updated with
// a stable signature, check that the two
// ObjectValues references the same Slot. If
// they are not, then the referencing
// CompilationUnit needs to be recompiled.
if (!updatedWithStableSignature.containsKey(dependentSource.getName()) &&
(ascContext != null) &&
u.hasTypeInfo &&
dependentCompilationUnit.hasTypeInfo &&
referencesDifferentSlots(ascContext, u.typeInfo, qName, dependentCompilationUnit.typeInfo))
{
reasons.put(s.getName(), l10n.getLocalizedTextString(new DependencyUpdated(dependentName.toString())));
valid = false;
}
}
}
else if (u.hasTypeInfo)
{
reasons.put(s.getName(), l10n.getLocalizedTextString(new DependencyNotCached(dependentName.toString())));
valid = false;
}
}
if (!valid)
{
affected.put(s.getName(), s);
iterator.remove();
break;
}
}
if (!swcSources.contains(s))
{
// only check the following when strict is enabled.
valid = valid && strict;
for (Iterator k = u.importPackageStatements.iterator(); valid && k.hasNext();)
{
String packageName = (String) k.next();
if (!hasPackage(sourcePath, swcContext, packageName))
{
affected.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new InvalidImportStatement(packageName)));
iterator.remove();
namespaces.add(packageName);
valid = false;
break;
}
}
for (Iterator k = u.importDefinitionStatements.iterator(); valid && k.hasNext();)
{
QName defName = (QName) k.next();
if (!hasDefinition(sourcePath, swcContext, defName))
{
affected.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new InvalidImportStatement(defName.toString())));
iterator.remove();
namespaces.add(defName.toString());
valid = false;
break;
}
}
}
}
// - DependentFileModified
if (strict)
{
Map<String, Source> updatedAndAffected = new HashMap<String, Source>(updated);
updatedAndAffected.putAll(affected);
for (Source source : updatedAndAffected.values())
{
dependentFileModified(source, dependents, updated, affected, reasons, sources);
}
}
for (Iterator<String> i = includeUpdated.iterator(); i.hasNext();)
{
ThreadLocalToolkit.getLogger().includedFileUpdated(i.next());
}
int affectedCount = affected.size();
logReasonAndRemoveCompilationUnit(affected, reasons, includeUpdated, swcContext);
logReasonAndRemoveCompilationUnit(updated, reasons, includeUpdated, swcContext);
// If a source was updated with a stable signature, then we need to seed ASCs userDefined
// with the definitions from that source. This is because we will recompile only the source with the stable
// signature, and none of it's dependents. Those dependencies will point at the old TypeValues,
// but new TypeValues will be created when we recompile the source because they weren't in userDefined. By putting
// the old TypeValues in userDefined, when the Source is recompiled it will reuse that same TypeValue
// instance, instead of creating a new one.
// We do not have to do this for the updated or affected maps, because anything in those will force
// their dependencies to be recompiled.
seedUserDefined(updatedWithStableSignature.values(), ascContext, perCompileData);
logReasonAndRemoveCompilationUnit(updatedWithStableSignature, reasons, includeUpdated, swcContext);
// if a compilation unit becomes obsolete, its satellite compilation units in ResourceContainer
// must go away too.
for (Source s : resources.sources().values())
{
if (s != null)
{
String name = s.getNameForReporting();
if (affected.containsKey(name) || updated.containsKey(name))
{
s.removeCompilationUnit();
}
}
}
affected.clear();
// validate multinames
// - MultiNameMeaningChanged
for (Iterator<Source> iterator = sources.iterator(); iterator.hasNext();)
{
Source s = iterator.next();
CompilationUnit u = s.getCompilationUnit();
if (u == null) continue;
for (Entry<MultiName, QName> entry : u.inheritanceHistory.entrySet())
{
MultiName multiName = entry.getKey();
QName qName = entry.getValue();
try
{
if (!validateMultiName(multiName, qName, sourcePath))
{
affected.put(s.getName(), s);
reasons.put(s.getName(), l10n.getLocalizedTextString(new MultiNameMeaningChanged(multiName, qName)));
iterator.remove();
}
}
catch (CompilerException ex)
{