/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.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.dart.engine.internal.context;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.dart.engine.AnalysisEngine;
import com.google.dart.engine.ast.AnnotatedNode;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.Comment;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.visitor.NodeLocator;
import com.google.dart.engine.constant.DeclaredVariables;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.context.AnalysisContextStatistics;
import com.google.dart.engine.context.AnalysisDelta;
import com.google.dart.engine.context.AnalysisDelta.AnalysisLevel;
import com.google.dart.engine.context.AnalysisErrorInfo;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.context.AnalysisListener;
import com.google.dart.engine.context.AnalysisOptions;
import com.google.dart.engine.context.AnalysisResult;
import com.google.dart.engine.context.ChangeNotice;
import com.google.dart.engine.context.ChangeSet;
import com.google.dart.engine.context.ObsoleteSourceAnalysisException;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementLocation;
import com.google.dart.engine.element.HtmlElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.angular.AngularComponentElement;
import com.google.dart.engine.element.angular.AngularElement;
import com.google.dart.engine.element.angular.AngularHasTemplateElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.html.ast.HtmlUnit;
import com.google.dart.engine.internal.cache.AnalysisCache;
import com.google.dart.engine.internal.cache.CachePartition;
import com.google.dart.engine.internal.cache.CacheRetentionPolicy;
import com.google.dart.engine.internal.cache.CacheState;
import com.google.dart.engine.internal.cache.DartEntry;
import com.google.dart.engine.internal.cache.DartEntryImpl;
import com.google.dart.engine.internal.cache.DataDescriptor;
import com.google.dart.engine.internal.cache.HtmlEntry;
import com.google.dart.engine.internal.cache.HtmlEntryImpl;
import com.google.dart.engine.internal.cache.RetentionPriority;
import com.google.dart.engine.internal.cache.SourceEntry;
import com.google.dart.engine.internal.cache.SourceEntryImpl;
import com.google.dart.engine.internal.cache.UniversalCachePartition;
import com.google.dart.engine.internal.element.ElementImpl;
import com.google.dart.engine.internal.element.LibraryElementImpl;
import com.google.dart.engine.internal.element.angular.AngularApplication;
import com.google.dart.engine.internal.resolver.Library;
import com.google.dart.engine.internal.resolver.LibraryResolver;
import com.google.dart.engine.internal.resolver.LibraryResolver2;
import com.google.dart.engine.internal.resolver.ResolvableLibrary;
import com.google.dart.engine.internal.resolver.TypeProvider;
import com.google.dart.engine.internal.resolver.TypeProviderImpl;
import com.google.dart.engine.internal.scope.Namespace;
import com.google.dart.engine.internal.scope.NamespaceBuilder;
import com.google.dart.engine.internal.task.AnalysisTask;
import com.google.dart.engine.internal.task.AnalysisTaskVisitor;
import com.google.dart.engine.internal.task.GenerateDartErrorsTask;
import com.google.dart.engine.internal.task.GenerateDartHintsTask;
import com.google.dart.engine.internal.task.GetContentTask;
import com.google.dart.engine.internal.task.IncrementalAnalysisTask;
import com.google.dart.engine.internal.task.ParseDartTask;
import com.google.dart.engine.internal.task.ParseHtmlTask;
import com.google.dart.engine.internal.task.PolymerBuildHtmlTask;
import com.google.dart.engine.internal.task.PolymerResolveHtmlTask;
import com.google.dart.engine.internal.task.ResolveAngularComponentTemplateTask;
import com.google.dart.engine.internal.task.ResolveAngularEntryHtmlTask;
import com.google.dart.engine.internal.task.ResolveDartLibraryCycleTask;
import com.google.dart.engine.internal.task.ResolveDartLibraryTask;
import com.google.dart.engine.internal.task.ResolveDartUnitTask;
import com.google.dart.engine.internal.task.ResolveHtmlTask;
import com.google.dart.engine.internal.task.ScanDartTask;
import com.google.dart.engine.internal.task.WaitForAsyncTask;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.sdk.DartSdk;
import com.google.dart.engine.source.ContentCache;
import com.google.dart.engine.source.FileBasedSource;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.source.Source.ContentReceiver;
import com.google.dart.engine.source.SourceContainer;
import com.google.dart.engine.source.SourceFactory;
import com.google.dart.engine.source.SourceKind;
import com.google.dart.engine.utilities.collection.DirectedGraph;
import com.google.dart.engine.utilities.collection.ListUtilities;
import com.google.dart.engine.utilities.collection.MapIterator;
import com.google.dart.engine.utilities.io.PrintStringWriter;
import com.google.dart.engine.utilities.source.LineInfo;
import com.google.dart.engine.utilities.translation.DartExpressionBody;
import com.google.dart.engine.utilities.translation.DartOmit;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Instances of the class {@code AnalysisContextImpl} implement an {@link AnalysisContext analysis
* context}.
*
* @coverage dart.engine
*/
public class AnalysisContextImpl implements InternalAnalysisContext {
/**
* Instances of the class {@code AnalysisTaskResultRecorder} are used by an analysis context to
* record the results of a task.
*/
private class AnalysisTaskResultRecorder implements AnalysisTaskVisitor<SourceEntry> {
@Override
public DartEntry visitGenerateDartErrorsTask(GenerateDartErrorsTask task)
throws AnalysisException {
return recordGenerateDartErrorsTask(task);
}
@Override
public DartEntry visitGenerateDartHintsTask(GenerateDartHintsTask task)
throws AnalysisException {
return recordGenerateDartHintsTask(task);
}
@Override
public SourceEntry visitGetContentTask(GetContentTask task) throws AnalysisException {
return recordGetContentsTask(task);
}
@Override
public DartEntry visitIncrementalAnalysisTask(IncrementalAnalysisTask task)
throws AnalysisException {
return recordIncrementalAnalysisTaskResults(task);
}
@Override
public DartEntry visitParseDartTask(ParseDartTask task) throws AnalysisException {
return recordParseDartTaskResults(task);
}
@Override
public HtmlEntry visitParseHtmlTask(ParseHtmlTask task) throws AnalysisException {
return recordParseHtmlTaskResults(task);
}
@Override
public HtmlEntry visitPolymerBuildHtmlTask(PolymerBuildHtmlTask task) throws AnalysisException {
return recordPolymerBuildHtmlTaskResults(task);
}
@Override
public HtmlEntry visitPolymerResolveHtmlTask(PolymerResolveHtmlTask task)
throws AnalysisException {
return recordPolymerResolveHtmlTaskResults(task);
}
@Override
public HtmlEntry visitResolveAngularComponentTemplateTask(
ResolveAngularComponentTemplateTask task) throws AnalysisException {
return recordResolveAngularComponentTemplateTaskResults(task);
}
@Override
public HtmlEntry visitResolveAngularEntryHtmlTask(ResolveAngularEntryHtmlTask task)
throws AnalysisException {
return recordResolveAngularEntryHtmlTaskResults(task);
}
@Override
public DartEntry visitResolveDartLibraryCycleTask(ResolveDartLibraryCycleTask task)
throws AnalysisException {
return recordResolveDartLibraryCycleTaskResults(task);
}
@Override
public DartEntry visitResolveDartLibraryTask(ResolveDartLibraryTask task)
throws AnalysisException {
return recordResolveDartLibraryTaskResults(task);
}
@Override
public DartEntry visitResolveDartUnitTask(ResolveDartUnitTask task) throws AnalysisException {
return recordResolveDartUnitTaskResults(task);
}
@Override
public HtmlEntry visitResolveHtmlTask(ResolveHtmlTask task) throws AnalysisException {
return recordResolveHtmlTaskResults(task);
}
@Override
public DartEntry visitScanDartTask(ScanDartTask task) throws AnalysisException {
return recordScanDartTaskResults(task);
}
}
private class ContextRetentionPolicy implements CacheRetentionPolicy {
@Override
public RetentionPriority getAstPriority(Source source, SourceEntry sourceEntry) {
int priorityCount = priorityOrder.length;
for (int i = 0; i < priorityCount; i++) {
if (source.equals(priorityOrder[i])) {
return RetentionPriority.HIGH;
}
}
if (neededForResolution != null && neededForResolution.contains(source)) {
return RetentionPriority.HIGH;
}
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
if (astIsNeeded(dartEntry)) {
return RetentionPriority.MEDIUM;
}
}
return RetentionPriority.LOW;
}
private boolean astIsNeeded(DartEntry dartEntry) {
return dartEntry.hasInvalidData(DartEntry.HINTS)
|| dartEntry.hasInvalidData(DartEntry.VERIFICATION_ERRORS)
|| dartEntry.hasInvalidData(DartEntry.RESOLUTION_ERRORS);
}
}
/**
* Instances of the class {@code CycleBuilder} are used to construct a list of the libraries that
* must be resolved together in order to resolve any one of the libraries.
*/
private class CycleBuilder {
/**
* Instances of the class {@code LibraryPair} hold a library and a list of the (source, entry)
* pairs for compilation units in the library.
*/
private/*static*/class LibraryPair {
/**
* The library containing the compilation units.
*/
public ResolvableLibrary library;
/**
* The (source, entry) pairs representing the compilation units in the library.
*/
public ArrayList<SourceEntryPair> entryPairs;
/**
* Initialize a newly created pair.
*
* @param library the library containing the compilation units
* @param entryPairs the (source, entry) pairs representing the compilation units in the
* library
*/
public LibraryPair(ResolvableLibrary library, ArrayList<SourceEntryPair> entryPairs) {
this.library = library;
this.entryPairs = entryPairs;
}
}
/**
* Instances of the class {@code SourceEntryPair} hold a source and the cache entry associated
* with that source. They are used to reduce the number of times an entry must be looked up in
* the {@link cache}.
*/
private/*static*/class SourceEntryPair {
/**
* The source associated with the entry.
*/
public Source source;
/**
* The entry associated with the source.
*/
public DartEntry entry;
/**
* Initialize a newly created pair.
*
* @param source the source associated with the entry
* @param entry the entry associated with the source
*/
public SourceEntryPair(Source source, DartEntry entry) {
this.source = source;
this.entry = entry;
}
}
/**
* A table mapping the sources of the defining compilation units of libraries to the
* representation of the library that has the information needed to resolve the library.
*/
private HashMap<Source, ResolvableLibrary> libraryMap = new HashMap<Source, ResolvableLibrary>();
/**
* The dependency graph used to compute the libraries in the cycle.
*/
// TODO(brianwilkerson) Explore the possibility of maintaining a single dependency graph for the
// whole context. Instead of building the graph, this class would merely determine whether the
// graph was complete enough to find the cycle containing a given library.
private DirectedGraph<ResolvableLibrary> dependencyGraph;
/**
* A list containing the libraries that are ready to be resolved.
*/
private List<ResolvableLibrary> librariesInCycle;
/**
* The analysis task that needs to be performed before the cycle of libraries can be resolved,
* or {@code null} if the libraries are ready to be resolved.
*/
private TaskData taskData;
/**
* Initialize a newly created cycle builder.
*/
public CycleBuilder() {
super();
}
/**
* Compute a list of the libraries that need to be resolved together in order to resolve the
* given library.
*
* @param librarySource the source of the library to be resolved
* @throws AnalysisException if the core library cannot be found
*/
public void computeCycleContaining(Source librarySource) throws AnalysisException {
//
// Create the object representing the library being resolved.
//
ResolvableLibrary targetLibrary = createLibrary(librarySource);
//
// Compute the set of libraries that need to be resolved together.
//
dependencyGraph = new DirectedGraph<ResolvableLibrary>();
computeLibraryDependencies(targetLibrary);
if (taskData != null) {
return;
}
librariesInCycle = dependencyGraph.findCycleContaining(targetLibrary);
//
// Ensure that all of the data needed to resolve them has been computed.
//
ensureImportsAndExports();
if (taskData != null) {
// At least one imported library needs to be resolved before the target library.
AnalysisTask task = taskData.getTask();
if (task instanceof ResolveDartLibraryTask) {
workManager.addFirst(
((ResolveDartLibraryTask) task).getLibrarySource(),
SourcePriority.LIBRARY);
}
return;
}
computePartsInCycle(librarySource);
if (taskData != null) {
// At least one part needs to be parsed.
return;
}
// All of the AST's necessary to perform a resolution of the library cycle have been
// gathered, so it is no longer necessary to retain them in the cache.
neededForResolution = null;
}
/**
* Return a list containing the libraries that are ready to be resolved (assuming that
* {@link #getTaskData()} returns {@code null}).
*
* @return the libraries that are ready to be resolved
*/
public List<ResolvableLibrary> getLibrariesInCycle() {
return librariesInCycle;
}
/**
* Return a representation of an analysis task that needs to be performed before the cycle of
* libraries can be resolved, or {@code null} if the libraries are ready to be resolved.
*
* @return the analysis task that needs to be performed before the cycle of libraries can be
* resolved
*/
public TaskData getTaskData() {
return taskData;
}
/**
* Recursively traverse the libraries reachable from the given library, creating instances of
* the class {@link Library} to represent them, and record the references in the library
* objects.
*
* @param library the library to be processed to find libraries that have not yet been traversed
* @throws AnalysisException if some portion of the library graph could not be traversed
*/
private void computeLibraryDependencies(ResolvableLibrary library) {
Source librarySource = library.getLibrarySource();
DartEntry dartEntry = getReadableDartEntry(librarySource);
Source[] importedSources = getSources(librarySource, dartEntry, DartEntry.IMPORTED_LIBRARIES);
if (taskData != null) {
return;
}
Source[] exportedSources = getSources(librarySource, dartEntry, DartEntry.EXPORTED_LIBRARIES);
if (taskData != null) {
return;
}
computeLibraryDependenciesFromDirectives(library, importedSources, exportedSources);
}
/**
* Recursively traverse the libraries reachable from the given library, creating instances of
* the class {@link Library} to represent them, and record the references in the library
* objects.
*
* @param library the library to be processed to find libraries that have not yet been traversed
* @param importedSources an array containing the sources that are imported into the given
* library
* @param exportedSources an array containing the sources that are exported from the given
* library
*/
private void computeLibraryDependenciesFromDirectives(ResolvableLibrary library,
Source[] importedSources, Source[] exportedSources) {
int importCount = importedSources.length;
if (importCount > 0) {
ArrayList<ResolvableLibrary> importedLibraries = new ArrayList<ResolvableLibrary>();
boolean explicitlyImportsCore = false;
for (int i = 0; i < importCount; i++) {
Source importedSource = importedSources[i];
if (importedSource.equals(coreLibrarySource)) {
explicitlyImportsCore = true;
}
ResolvableLibrary importedLibrary = libraryMap.get(importedSource);
if (importedLibrary == null) {
importedLibrary = createLibraryOrNull(importedSource);
if (importedLibrary != null) {
computeLibraryDependencies(importedLibrary);
if (taskData != null) {
return;
}
}
}
if (importedLibrary != null) {
importedLibraries.add(importedLibrary);
dependencyGraph.addEdge(library, importedLibrary);
}
}
library.setExplicitlyImportsCore(explicitlyImportsCore);
if (!explicitlyImportsCore && !coreLibrarySource.equals(library.getLibrarySource())) {
ResolvableLibrary importedLibrary = libraryMap.get(coreLibrarySource);
if (importedLibrary == null) {
importedLibrary = createLibraryOrNull(coreLibrarySource);
if (importedLibrary != null) {
computeLibraryDependencies(importedLibrary);
if (taskData != null) {
return;
}
}
}
if (importedLibrary != null) {
importedLibraries.add(importedLibrary);
dependencyGraph.addEdge(library, importedLibrary);
}
}
library.setImportedLibraries(importedLibraries.toArray(new ResolvableLibrary[importedLibraries.size()]));
} else {
library.setExplicitlyImportsCore(false);
ResolvableLibrary importedLibrary = libraryMap.get(coreLibrarySource);
if (importedLibrary == null) {
importedLibrary = createLibraryOrNull(coreLibrarySource);
if (importedLibrary != null) {
computeLibraryDependencies(importedLibrary);
if (taskData != null) {
return;
}
}
}
if (importedLibrary != null) {
dependencyGraph.addEdge(library, importedLibrary);
library.setImportedLibraries(new ResolvableLibrary[] {importedLibrary});
}
}
int exportCount = exportedSources.length;
if (exportCount > 0) {
ArrayList<ResolvableLibrary> exportedLibraries = new ArrayList<ResolvableLibrary>(
exportCount);
for (int i = 0; i < exportCount; i++) {
Source exportedSource = exportedSources[i];
ResolvableLibrary exportedLibrary = libraryMap.get(exportedSource);
if (exportedLibrary == null) {
exportedLibrary = createLibraryOrNull(exportedSource);
if (exportedLibrary != null) {
computeLibraryDependencies(exportedLibrary);
if (taskData != null) {
return;
}
}
}
if (exportedLibrary != null) {
exportedLibraries.add(exportedLibrary);
dependencyGraph.addEdge(library, exportedLibrary);
}
}
library.setExportedLibraries(exportedLibraries.toArray(new ResolvableLibrary[exportedLibraries.size()]));
}
}
/**
* Gather the resolvable AST structures for each of the compilation units in each of the
* libraries in the cycle. This is done in two phases: first we ensure that we have cached an
* AST structure for each compilation unit, then we gather them. We split the work this way
* because getting the AST structures can change the state of the cache in such a way that we
* would have more work to do if any compilation unit didn't have a resolvable AST structure.
*/
private void computePartsInCycle(Source librarySource) throws AnalysisException {
int count = librariesInCycle.size();
ArrayList<LibraryPair> libraryData = new ArrayList<LibraryPair>(count);
for (int i = 0; i < count; i++) {
ResolvableLibrary library = librariesInCycle.get(i);
libraryData.add(new LibraryPair(library, ensurePartsInLibrary(library)));
}
neededForResolution = gatherSources(libraryData);
if (TRACE_PERFORM_TASK) {
System.out.println(" preserve resolution data for " + neededForResolution.size()
+ " sources while resolving " + librarySource.getFullName());
}
if (taskData != null) {
return;
}
for (int i = 0; i < count; i++) {
computePartsInLibrary(libraryData.get(i));
}
}
/**
* Gather the resolvable compilation units for each of the compilation units in the specified
* library.
*
* @param libraryPair a holder containing both the library and a list of (source, entry) pairs
* for all of the compilation units in the library
*/
private void computePartsInLibrary(LibraryPair libraryPair) {
ResolvableLibrary library = libraryPair.library;
ArrayList<SourceEntryPair> entryPairs = libraryPair.entryPairs;
int count = entryPairs.size();
ResolvableCompilationUnit[] units = new ResolvableCompilationUnit[count];
for (int i = 0; i < count; i++) {
SourceEntryPair entryPair = entryPairs.get(i);
Source source = entryPair.source;
DartEntryImpl dartCopy = entryPair.entry.getWritableCopy();
units[i] = new ResolvableCompilationUnit(
dartCopy.getModificationTime(),
dartCopy.getResolvableCompilationUnit(),
source);
cache.put(source, dartCopy);
}
library.setResolvableCompilationUnits(units);
}
/**
* Create an object to represent the information about the library defined by the compilation
* unit with the given source.
*
* @param librarySource the source of the library's defining compilation unit
* @return the library object that was created
*/
private ResolvableLibrary createLibrary(Source librarySource) {
ResolvableLibrary library = new ResolvableLibrary(librarySource);
SourceEntry sourceEntry = cache.get(librarySource);
if (sourceEntry instanceof DartEntry) {
LibraryElementImpl libraryElement = (LibraryElementImpl) sourceEntry.getValue(DartEntry.ELEMENT);
if (libraryElement != null) {
library.setLibraryElement(libraryElement);
}
}
libraryMap.put(librarySource, library);
return library;
}
/**
* Create an object to represent the information about the library defined by the compilation
* unit with the given source.
*
* @param librarySource the source of the library's defining compilation unit
* @return the library object that was created
*/
private ResolvableLibrary createLibraryOrNull(Source librarySource) {
ResolvableLibrary library = new ResolvableLibrary(librarySource);
SourceEntry sourceEntry = cache.get(librarySource);
if (sourceEntry instanceof DartEntry) {
LibraryElementImpl libraryElement = (LibraryElementImpl) sourceEntry.getValue(DartEntry.ELEMENT);
if (libraryElement != null) {
library.setLibraryElement(libraryElement);
}
}
libraryMap.put(librarySource, library);
return library;
}
/**
* Ensure that the given library has an element model built for it. If another task needs to be
* executed first in order to build the element model, that task is placed in {@link #taskData}.
*
* @param library the library which needs an element model.
*/
private void ensureElementModel(ResolvableLibrary library) {
Source librarySource = library.getLibrarySource();
DartEntry libraryEntry = getReadableDartEntry(librarySource);
if (libraryEntry != null && libraryEntry.getState(DartEntry.PARSED_UNIT) != CacheState.ERROR) {
workManager.addFirst(librarySource, SourcePriority.LIBRARY);
if (taskData == null) {
taskData = createResolveDartLibraryTask(librarySource, libraryEntry);
}
}
}
/**
* Ensure that all of the libraries that are exported by the given library (but are not
* themselves in the cycle) have element models built for them. If another task needs to be
* executed first in order to build the element model, that task is placed in {@link #taskData}.
*
* @param library the library being tested
*/
private void ensureExports(ResolvableLibrary library, HashSet<Source> visitedLibraries) {
ResolvableLibrary[] dependencies = library.getExports();
int dependencyCount = dependencies.length;
for (int i = 0; i < dependencyCount; i++) {
ResolvableLibrary dependency = dependencies[i];
if (!librariesInCycle.contains(dependency)
&& visitedLibraries.add(dependency.getLibrarySource())) {
if (dependency.getLibraryElement() == null) {
ensureElementModel(dependency);
} else {
ensureExports(dependency, visitedLibraries);
}
if (taskData != null) {
return;
}
}
}
}
/**
* Ensure that all of the libraries that are exported by the given library (but are not
* themselves in the cycle) have element models built for them. If another task needs to be
* executed first in order to build the element model, that task is placed in {@link #taskData}.
*
* @param library the library being tested
*/
private void ensureImports(ResolvableLibrary library) {
ResolvableLibrary[] dependencies = library.getImports();
int dependencyCount = dependencies.length;
for (int i = 0; i < dependencyCount; i++) {
ResolvableLibrary dependency = dependencies[i];
if (!librariesInCycle.contains(dependency) && dependency.getLibraryElement() == null) {
ensureElementModel(dependency);
if (taskData != null) {
return;
}
}
}
}
/**
* Ensure that all of the libraries that are either imported or exported by libraries in the
* cycle (but are not themselves in the cycle) have element models built for them.
*/
private void ensureImportsAndExports() {
HashSet<Source> visitedLibraries = new HashSet<Source>();
int libraryCount = librariesInCycle.size();
for (int i = 0; i < libraryCount; i++) {
ResolvableLibrary library = librariesInCycle.get(i);
ensureImports(library);
if (taskData != null) {
return;
}
ensureExports(library, visitedLibraries);
if (taskData != null) {
return;
}
}
}
/**
* Ensure that there is a resolvable compilation unit available for all of the compilation units
* in the given library.
*
* @param library the library for which resolvable compilation units must be available
* @return a list of (source, entry) pairs for all of the compilation units in the library
*/
private ArrayList<SourceEntryPair> ensurePartsInLibrary(ResolvableLibrary library)
throws AnalysisException {
ArrayList<SourceEntryPair> pairs = new ArrayList<SourceEntryPair>();
Source librarySource = library.getLibrarySource();
DartEntry libraryEntry = getReadableDartEntry(librarySource);
if (libraryEntry == null) {
throw new AnalysisException("Cannot find entry for " + librarySource.getFullName());
} else if (libraryEntry.getState(DartEntry.PARSED_UNIT) == CacheState.ERROR) {
String message = "Cannot compute parsed unit for " + librarySource.getFullName();
AnalysisException exception = libraryEntry.getException();
if (exception == null) {
throw new AnalysisException(message);
}
throw new AnalysisException(message, exception);
}
ensureResolvableCompilationUnit(librarySource, libraryEntry);
pairs.add(new SourceEntryPair(librarySource, libraryEntry));
Source[] partSources = getSources(librarySource, libraryEntry, DartEntry.INCLUDED_PARTS);
int count = partSources.length;
for (int i = 0; i < count; i++) {
Source partSource = partSources[i];
DartEntry partEntry = getReadableDartEntry(partSource);
if (partEntry != null && partEntry.getState(DartEntry.PARSED_UNIT) != CacheState.ERROR) {
ensureResolvableCompilationUnit(partSource, partEntry);
pairs.add(new SourceEntryPair(partSource, partEntry));
}
}
return pairs;
}
/**
* Ensure that there is a resolvable compilation unit available for the given source.
*
* @param source the source for which a resolvable compilation unit must be available
* @param dartEntry the entry associated with the source
*/
private void ensureResolvableCompilationUnit(Source source, DartEntry dartEntry) {
// The entry will be null if the source represents a non-Dart file.
if (dartEntry != null && !dartEntry.hasResolvableCompilationUnit()) {
if (taskData == null) {
taskData = createParseDartTask(source, dartEntry);
}
}
}
private HashSet<Source> gatherSources(ArrayList<LibraryPair> libraryData) {
int libraryCount = libraryData.size();
HashSet<Source> sources = new HashSet<Source>(libraryCount * 2);
for (int i = 0; i < libraryCount; i++) {
ArrayList<SourceEntryPair> entryPairs = libraryData.get(i).entryPairs;
int entryCount = entryPairs.size();
for (int j = 0; j < entryCount; j++) {
sources.add(entryPairs.get(j).source);
}
}
return sources;
}
/**
* Return the sources described by the given descriptor.
*
* @param source the source with which the sources are associated
* @param dartEntry the entry corresponding to the source
* @param descriptor the descriptor indicating which sources are to be returned
* @return the sources described by the given descriptor
*/
private Source[] getSources(Source source, DartEntry dartEntry,
DataDescriptor<Source[]> descriptor) {
if (dartEntry == null) {
return Source.EMPTY_ARRAY;
}
CacheState exportState = dartEntry.getState(descriptor);
if (exportState == CacheState.ERROR) {
return Source.EMPTY_ARRAY;
} else if (exportState != CacheState.VALID) {
if (taskData == null) {
taskData = createParseDartTask(source, dartEntry);
}
return Source.EMPTY_ARRAY;
}
return dartEntry.getValue(descriptor);
}
}
/**
* Instances of the class {@code TaskData} represent information about the next task to be
* performed. Each data has an implicit associated source: the source that might need to be
* analyzed. There are essentially three states that can be represented:
* <ul>
* <li>If {@link #getTask()} returns a non-{@code null} value, then that is the task that should
* be executed to further analyze the associated source.
* <li>Otherwise, if {@link #isBlocked()} returns {@code true}, then there is no work that can be
* done, but analysis for the associated source is not complete.
* <li>Otherwise, {@link #getDependentSource()} should return a source that needs to be analyzed
* before the analysis of the associated source can be completed.
* </ul>
*/
private static class TaskData {
/**
* The task that is to be performed.
*/
private AnalysisTask task;
/**
* A flag indicating whether the associated source is blocked waiting for its contents to be
* loaded.
*/
private boolean blocked;
/**
* Initialize a newly created data holder.
*
* @param task the task that is to be performed
* @param blocked {@code true} if the associated source is blocked waiting for its contents to
* be loaded
*/
public TaskData(AnalysisTask task, boolean blocked) {
this.task = task;
this.blocked = blocked;
}
/**
* Return the task that is to be performed, or {@code null} if there is no task associated with
* the source.
*
* @return the task that is to be performed
*/
public AnalysisTask getTask() {
return task;
}
/**
* Return {@code true} if the associated source is blocked waiting for its contents to be
* loaded.
*
* @return {@code true} if the associated source is blocked waiting for its contents to be
* loaded
*/
public boolean isBlocked() {
return blocked;
}
@Override
public String toString() {
if (task == null) {
return "blocked: " + blocked;
}
return task.toString();
}
}
/**
* The difference between the maximum cache size and the maximum priority order size. The priority
* list must be capped so that it is less than the cache size. Failure to do so can result in an
* infinite loop in performAnalysisTask() because re-caching one AST structure can cause another
* priority source's AST structure to be flushed.
*/
private static final int PRIORITY_ORDER_SIZE_DELTA = 4;
/**
* A flag indicating whether trace output should be produced as analysis tasks are performed. Used
* for debugging.
*/
private static final boolean TRACE_PERFORM_TASK = false;
/**
* The set of analysis options controlling the behavior of this context.
*/
private AnalysisOptionsImpl options = new AnalysisOptionsImpl();
/**
* A flag indicating whether errors related to sources in the SDK should be generated and
* reported.
*/
boolean generateSdkErrors = true;
/**
* A flag indicating whether this context is disposed.
*/
private boolean disposed;
/**
* A cache of content used to override the default content of a source.
*/
private ContentCache contentCache = new ContentCache();
/**
* The source factory used to create the sources that can be analyzed in this context.
*/
private SourceFactory sourceFactory;
/**
* The set of declared variables used when computing constant values.
*/
private DeclaredVariables declaredVariables = new DeclaredVariables();
/**
* A source representing the core library.
*/
private Source coreLibrarySource;
/**
* The partition that contains analysis results that are not shared with other contexts.
*/
private CachePartition privatePartition;
/**
* A table mapping the sources known to the context to the information known about the source.
*/
private AnalysisCache cache;
/**
* An array containing sources for which data should not be flushed.
*/
private Source[] priorityOrder = Source.EMPTY_ARRAY;
/**
* An array containing sources whose AST structure is needed in order to resolve the next library
* to be resolved.
*/
private HashSet<Source> neededForResolution = null;
/**
* A table mapping sources to the change notices that are waiting to be returned related to that
* source.
*/
private HashMap<Source, ChangeNoticeImpl> pendingNotices = new HashMap<Source, ChangeNoticeImpl>();
/**
* The object used to synchronize access to all of the caches. The rules related to the use of
* this lock object are
* <ul>
* <li>no analysis work is done while holding the lock, and</li>
* <li>no analysis results can be recorded unless we have obtained the lock and validated that the
* results are for the same version (modification time) of the source as our current cache
* content.</li>
* </ul>
*/
private static Object cacheLock = new Object();
/**
* The object used to record the results of performing an analysis task.
*/
private AnalysisTaskResultRecorder resultRecorder;
/**
* Cached information used in incremental analysis or {@code null} if none. Synchronize against
* {@link #cacheLock} before accessing this field.
*/
private IncrementalAnalysisCache incrementalAnalysisCache;
/**
* The object used to manage the list of sources that need to be analyzed.
*/
private WorkManager workManager = new WorkManager();
/**
* The set of {@link AngularApplication} in this context.
*/
private final Set<AngularApplication> angularApplications = Sets.newHashSet();
/**
* The listeners that are to be notified when various analysis results are produced in this
* context.
*/
private ArrayList<AnalysisListener> listeners = new ArrayList<AnalysisListener>();
/**
* Initialize a newly created analysis context.
*/
public AnalysisContextImpl() {
super();
resultRecorder = new AnalysisTaskResultRecorder();
privatePartition = new UniversalCachePartition(
this,
AnalysisOptionsImpl.DEFAULT_CACHE_SIZE,
new ContextRetentionPolicy());
cache = createCacheFromSourceFactory(null);
}
@Override
public void addListener(AnalysisListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
@Override
public void addSourceInfo(Source source, SourceEntry info) {
// This implementation assumes that the access to the cache does not need to be synchronized
// because no other object can have access to this context while this method is being invoked.
cache.put(source, info);
}
@Override
public void applyAnalysisDelta(AnalysisDelta delta) {
ChangeSet changeSet = new ChangeSet();
for (Entry<Source, AnalysisLevel> entry : delta.getAnalysisLevels().entrySet()) {
Source source = entry.getKey();
if (entry.getValue() == AnalysisLevel.NONE) {
changeSet.removedSource(source);
} else {
changeSet.addedSource(source);
}
}
applyChanges(changeSet);
}
@Override
public void applyChanges(ChangeSet changeSet) {
if (changeSet.isEmpty()) {
return;
}
synchronized (cacheLock) {
//
// First, compute the list of sources that have been removed.
//
ArrayList<Source> removedSources = new ArrayList<Source>(changeSet.getRemovedSources());
for (SourceContainer container : changeSet.getRemovedContainers()) {
addSourcesInContainer(removedSources, container);
}
//
// Then determine which cached results are no longer valid.
//
boolean addedDartSource = false;
for (Source source : changeSet.getAddedSources()) {
if (sourceAvailable(source)) {
addedDartSource = true;
}
}
for (Source source : changeSet.getChangedSources()) {
if (contentCache.getContents(source) != null) {
// This source is overridden in the content cache, so the change will have no effect.
// Just ignore it to avoid wasting time doing re-analysis.
continue;
}
sourceChanged(source);
}
for (Map.Entry<Source, String> entry : changeSet.getChangedContents().entrySet()) {
setContents(entry.getKey(), entry.getValue());
}
for (Map.Entry<Source, ChangeSet.ContentChange> entry : changeSet.getChangedRanges().entrySet()) {
ChangeSet.ContentChange change = entry.getValue();
setChangedContents(
entry.getKey(),
change.getContents(),
change.getOffset(),
change.getOldLength(),
change.getNewLength());
}
for (Source source : changeSet.getDeletedSources()) {
sourceDeleted(source);
}
for (Source source : removedSources) {
sourceRemoved(source);
}
if (addedDartSource) {
// TODO(brianwilkerson) This is hugely inefficient, but we need to re-analyze any libraries
// that might have been referencing the not-yet-existing source that was just added. Longer
// term we need to keep track of which libraries are referencing non-existing sources and
// only re-analyze those libraries.
// logInformation("Added Dart sources, invalidating all resolution information");
ArrayList<Source> sourcesToInvalidate = new ArrayList<Source>();
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
SourceEntry sourceEntry = iterator.getValue();
if (!source.isInSystemLibrary()
&& (sourceEntry instanceof DartEntry || sourceEntry instanceof HtmlEntry)) {
sourcesToInvalidate.add(source);
}
}
int count = sourcesToInvalidate.size();
for (int i = 0; i < count; i++) {
Source source = sourcesToInvalidate.get(i);
SourceEntry entry = getReadableSourceEntry(source);
if (entry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) entry;
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.invalidateAllResolutionInformation(false);
cache.put(source, dartCopy);
workManager.add(source, computePriority(dartCopy));
} else if (entry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) entry;
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.invalidateAllResolutionInformation(false);
cache.put(source, htmlCopy);
workManager.add(source, SourcePriority.HTML);
}
}
}
}
}
@Override
public String computeDocumentationComment(Element element) throws AnalysisException {
if (element == null) {
return null;
}
Source source = element.getSource();
if (source == null) {
return null;
}
CompilationUnit unit = parseCompilationUnit(source);
if (unit == null) {
return null;
}
NodeLocator locator = new NodeLocator(element.getNameOffset());
AstNode nameNode = locator.searchWithin(unit);
while (nameNode != null) {
if (nameNode instanceof AnnotatedNode) {
Comment comment = ((AnnotatedNode) nameNode).getDocumentationComment();
if (comment == null) {
return null;
}
StringBuilder builder = new StringBuilder();
Token[] tokens = comment.getTokens();
for (int i = 0; i < tokens.length; i++) {
if (i > 0) {
builder.append("\n");
}
builder.append(tokens[i].getLexeme());
}
return builder.toString();
}
nameNode = nameNode.getParent();
}
return null;
}
@Override
public AnalysisError[] computeErrors(Source source) throws AnalysisException {
boolean enableHints = options.getHint();
SourceEntry sourceEntry = getReadableSourceEntry(source);
if (sourceEntry instanceof DartEntry) {
ArrayList<AnalysisError> errors = new ArrayList<AnalysisError>();
try {
DartEntry dartEntry = (DartEntry) sourceEntry;
ListUtilities.addAll(errors, getDartScanData(source, dartEntry, DartEntry.SCAN_ERRORS));
dartEntry = getReadableDartEntry(source);
ListUtilities.addAll(errors, getDartParseData(source, dartEntry, DartEntry.PARSE_ERRORS));
dartEntry = getReadableDartEntry(source);
if (dartEntry.getValue(DartEntry.SOURCE_KIND) == SourceKind.LIBRARY) {
ListUtilities.addAll(
errors,
getDartResolutionData(source, source, dartEntry, DartEntry.RESOLUTION_ERRORS));
dartEntry = getReadableDartEntry(source);
ListUtilities.addAll(
errors,
getDartVerificationData(source, source, dartEntry, DartEntry.VERIFICATION_ERRORS));
if (enableHints) {
dartEntry = getReadableDartEntry(source);
ListUtilities.addAll(
errors,
getDartHintData(source, source, dartEntry, DartEntry.HINTS));
}
} else {
Source[] libraries = getLibrariesContaining(source);
for (Source librarySource : libraries) {
ListUtilities.addAll(
errors,
getDartResolutionData(source, librarySource, dartEntry, DartEntry.RESOLUTION_ERRORS));
dartEntry = getReadableDartEntry(source);
ListUtilities.addAll(
errors,
getDartVerificationData(
source,
librarySource,
dartEntry,
DartEntry.VERIFICATION_ERRORS));
if (enableHints) {
dartEntry = getReadableDartEntry(source);
ListUtilities.addAll(
errors,
getDartHintData(source, librarySource, dartEntry, DartEntry.HINTS));
}
}
}
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute errors",
exception);
}
if (errors.isEmpty()) {
return AnalysisError.NO_ERRORS;
}
return errors.toArray(new AnalysisError[errors.size()]);
} else if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
try {
return getHtmlResolutionData(source, htmlEntry, HtmlEntry.RESOLUTION_ERRORS);
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute errors",
exception);
}
}
return AnalysisError.NO_ERRORS;
}
@Override
public Source[] computeExportedLibraries(Source source) throws AnalysisException {
return getDartParseData(source, DartEntry.EXPORTED_LIBRARIES, Source.EMPTY_ARRAY);
}
@Override
public HtmlElement computeHtmlElement(Source source) throws AnalysisException {
return getHtmlResolutionData(source, HtmlEntry.ELEMENT, null);
}
@Override
public Source[] computeImportedLibraries(Source source) throws AnalysisException {
return getDartParseData(source, DartEntry.IMPORTED_LIBRARIES, Source.EMPTY_ARRAY);
}
@Override
public SourceKind computeKindOf(Source source) {
SourceEntry sourceEntry = getReadableSourceEntry(source);
if (sourceEntry == null) {
return SourceKind.UNKNOWN;
} else if (sourceEntry instanceof DartEntry) {
try {
return getDartParseData(source, (DartEntry) sourceEntry, DartEntry.SOURCE_KIND);
} catch (AnalysisException exception) {
return SourceKind.UNKNOWN;
}
}
return sourceEntry.getKind();
}
@Override
public LibraryElement computeLibraryElement(Source source) throws AnalysisException {
return getDartResolutionData(source, source, DartEntry.ELEMENT, null);
}
@Override
public LineInfo computeLineInfo(Source source) throws AnalysisException {
SourceEntry sourceEntry = getReadableSourceEntry(source);
try {
if (sourceEntry instanceof HtmlEntry) {
return getHtmlParseData(source, SourceEntry.LINE_INFO, null);
} else if (sourceEntry instanceof DartEntry) {
return getDartScanData(source, SourceEntry.LINE_INFO, null);
}
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute " + SourceEntry.LINE_INFO.toString(),
exception);
}
return null;
}
@Override
public ResolvableCompilationUnit computeResolvableCompilationUnit(Source source)
throws AnalysisException {
synchronized (cacheLock) {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
throw new AnalysisException("computeResolvableCompilationUnit for non-Dart: "
+ source.getFullName());
}
dartEntry = cacheDartParseData(source, dartEntry, DartEntry.PARSED_UNIT);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
CompilationUnit unit = dartCopy.getResolvableCompilationUnit();
if (unit == null) {
throw new AnalysisException(
"Internal error: computeResolvableCompilationUnit could not parse "
+ source.getFullName(),
dartEntry.getException());
}
cache.put(source, dartCopy);
return new ResolvableCompilationUnit(dartCopy.getModificationTime(), unit);
}
}
@Override
public void dispose() {
disposed = true;
}
@Override
public boolean exists(Source source) {
if (source == null) {
return false;
}
synchronized (cacheLock) {
if (contentCache.getContents(source) != null) {
return true;
}
}
return source.exists();
}
@Override
public AnalysisContext extractContext(SourceContainer container) {
return extractContextInto(
container,
(InternalAnalysisContext) AnalysisEngine.getInstance().createAnalysisContext());
}
@Override
public InternalAnalysisContext extractContextInto(SourceContainer container,
InternalAnalysisContext newContext) {
ArrayList<Source> sourcesToRemove = new ArrayList<Source>();
synchronized (cacheLock) {
// Move sources in the specified directory to the new context
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
SourceEntry sourceEntry = iterator.getValue();
if (container.contains(source)) {
sourcesToRemove.add(source);
newContext.addSourceInfo(source, sourceEntry.getWritableCopy());
}
}
// TODO (danrubel): Either remove sources or adjust contract described in AnalysisContext.
// Currently, callers assume that sources have been removed from this context
// for (Source source : sourcesToRemove) {
// // TODO(brianwilkerson) Determine whether the source should be removed (that is, whether
// // there are no additional dependencies on the source), and if so remove all information
// // about the source.
// sourceMap.remove(source);
// parseCache.remove(source);
// publicNamespaceCache.remove(source);
// libraryElementCache.remove(source);
// }
}
return newContext;
}
@Override
public AnalysisOptions getAnalysisOptions() {
return options;
}
@Override
public AngularApplication getAngularApplicationWithHtml(Source htmlSource) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(htmlSource);
if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
AngularApplication application = htmlEntry.getValue(HtmlEntry.ANGULAR_APPLICATION);
if (application != null) {
return application;
}
return htmlEntry.getValue(HtmlEntry.ANGULAR_ENTRY);
}
return null;
}
@Override
public CompilationUnitElement getCompilationUnitElement(Source unitSource, Source librarySource) {
LibraryElement libraryElement = getLibraryElement(librarySource);
if (libraryElement != null) {
// try defining unit
CompilationUnitElement definingUnit = libraryElement.getDefiningCompilationUnit();
if (definingUnit.getSource().equals(unitSource)) {
return definingUnit;
}
// try parts
for (CompilationUnitElement partUnit : libraryElement.getParts()) {
if (partUnit.getSource().equals(unitSource)) {
return partUnit;
}
}
}
return null;
}
@Override
public TimestampedData<CharSequence> getContents(Source source) throws Exception {
synchronized (cacheLock) {
String contents = contentCache.getContents(source);
if (contents != null) {
return new TimestampedData<CharSequence>(
contentCache.getModificationStamp(source),
contents);
}
}
return source.getContents();
}
@Override
@SuppressWarnings("deprecation")
@DartOmit
public void getContentsToReceiver(Source source, ContentReceiver receiver) throws Exception {
synchronized (cacheLock) {
String contents = contentCache.getContents(source);
if (contents != null) {
receiver.accept(contents, contentCache.getModificationStamp(source));
return;
}
}
source.getContentsToReceiver(receiver);
}
@Override
public InternalAnalysisContext getContextFor(Source source) {
synchronized (cacheLock) {
InternalAnalysisContext context = cache.getContextFor(source);
return context == null ? this : context;
}
}
@Override
public DeclaredVariables getDeclaredVariables() {
return declaredVariables;
}
@Override
public Element getElement(ElementLocation location) {
// TODO(brianwilkerson) This should not be a "get" method.
try {
String[] components = location.getComponents();
Source source = computeSourceFromEncoding(components[0]);
String sourceName = source.getShortName();
if (AnalysisEngine.isDartFileName(sourceName)) {
ElementImpl element = (ElementImpl) computeLibraryElement(source);
for (int i = 1; i < components.length; i++) {
if (element == null) {
return null;
}
element = element.getChild(components[i]);
}
return element;
}
if (AnalysisEngine.isHtmlFileName(sourceName)) {
return computeHtmlElement(source);
}
} catch (AnalysisException exception) {
}
return null;
}
@Override
public AnalysisErrorInfo getErrors(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
return new AnalysisErrorInfoImpl(
dartEntry.getAllErrors(),
dartEntry.getValue(SourceEntry.LINE_INFO));
} else if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
return new AnalysisErrorInfoImpl(
htmlEntry.getAllErrors(),
htmlEntry.getValue(SourceEntry.LINE_INFO));
}
return new AnalysisErrorInfoImpl(AnalysisError.NO_ERRORS, null);
}
@Override
public HtmlElement getHtmlElement(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry instanceof HtmlEntry) {
return ((HtmlEntry) sourceEntry).getValue(HtmlEntry.ELEMENT);
}
return null;
}
@Override
public Source[] getHtmlFilesReferencing(Source source) {
SourceKind sourceKind = getKindOf(source);
if (sourceKind == null) {
return Source.EMPTY_ARRAY;
}
synchronized (cacheLock) {
ArrayList<Source> htmlSources = new ArrayList<Source>();
switch (sourceKind) {
case LIBRARY:
default:
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry.getKind() == SourceKind.HTML) {
Source[] referencedLibraries = ((HtmlEntry) sourceEntry).getValue(HtmlEntry.REFERENCED_LIBRARIES);
if (contains(referencedLibraries, source)) {
htmlSources.add(iterator.getKey());
}
}
}
break;
case PART:
Source[] librarySources = getLibrariesContaining(source);
MapIterator<Source, SourceEntry> partIterator = cache.iterator();
while (partIterator.moveNext()) {
SourceEntry sourceEntry = partIterator.getValue();
if (sourceEntry.getKind() == SourceKind.HTML) {
Source[] referencedLibraries = ((HtmlEntry) sourceEntry).getValue(HtmlEntry.REFERENCED_LIBRARIES);
if (containsAny(referencedLibraries, librarySources)) {
htmlSources.add(partIterator.getKey());
}
}
}
break;
}
if (htmlSources.isEmpty()) {
return Source.EMPTY_ARRAY;
}
return htmlSources.toArray(new Source[htmlSources.size()]);
}
}
@Override
public Source[] getHtmlSources() {
return getSources(SourceKind.HTML);
}
@Override
public SourceKind getKindOf(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry == null) {
return SourceKind.UNKNOWN;
}
return sourceEntry.getKind();
}
@Override
public Source[] getLaunchableClientLibrarySources() {
// TODO(brianwilkerson) This needs to filter out libraries that do not reference dart:html,
// either directly or indirectly.
ArrayList<Source> sources = new ArrayList<Source>();
synchronized (cacheLock) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry.getKind() == SourceKind.LIBRARY && !source.isInSystemLibrary()) {
// DartEntry dartEntry = (DartEntry) sourceEntry;
// if (dartEntry.getValue(DartEntry.IS_LAUNCHABLE) && dartEntry.getValue(DartEntry.IS_CLIENT)) {
sources.add(source);
// }
}
}
}
return sources.toArray(new Source[sources.size()]);
}
@Override
public Source[] getLaunchableServerLibrarySources() {
// TODO(brianwilkerson) This needs to filter out libraries that reference dart:html, either
// directly or indirectly.
ArrayList<Source> sources = new ArrayList<Source>();
synchronized (cacheLock) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry.getKind() == SourceKind.LIBRARY && !source.isInSystemLibrary()) {
// DartEntry dartEntry = (DartEntry) sourceEntry;
// if (dartEntry.getValue(DartEntry.IS_LAUNCHABLE) && !dartEntry.getValue(DartEntry.IS_CLIENT)) {
sources.add(source);
// }
}
}
}
return sources.toArray(new Source[sources.size()]);
}
@Override
public Source[] getLibrariesContaining(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry instanceof DartEntry) {
return ((DartEntry) sourceEntry).getValue(DartEntry.CONTAINING_LIBRARIES);
}
return Source.EMPTY_ARRAY;
}
@Override
public Source[] getLibrariesDependingOn(Source librarySource) {
synchronized (cacheLock) {
ArrayList<Source> dependentLibraries = new ArrayList<Source>();
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry.getKind() == SourceKind.LIBRARY) {
if (contains(
((DartEntry) sourceEntry).getValue(DartEntry.EXPORTED_LIBRARIES),
librarySource)) {
dependentLibraries.add(iterator.getKey());
}
if (contains(
((DartEntry) sourceEntry).getValue(DartEntry.IMPORTED_LIBRARIES),
librarySource)) {
dependentLibraries.add(iterator.getKey());
}
}
}
if (dependentLibraries.isEmpty()) {
return Source.EMPTY_ARRAY;
}
return dependentLibraries.toArray(new Source[dependentLibraries.size()]);
}
}
@Override
public Source[] getLibrariesReferencedFromHtml(Source htmlSource) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(htmlSource);
if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
return htmlEntry.getValue(HtmlEntry.REFERENCED_LIBRARIES);
}
return Source.EMPTY_ARRAY;
}
@Override
public LibraryElement getLibraryElement(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry instanceof DartEntry) {
return ((DartEntry) sourceEntry).getValue(DartEntry.ELEMENT);
}
return null;
}
@Override
public Source[] getLibrarySources() {
return getSources(SourceKind.LIBRARY);
}
@Override
public LineInfo getLineInfo(Source source) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(source);
if (sourceEntry != null) {
return sourceEntry.getValue(SourceEntry.LINE_INFO);
}
return null;
}
@Override
public long getModificationStamp(Source source) {
synchronized (cacheLock) {
Long stamp = contentCache.getModificationStamp(source);
if (stamp != null) {
return stamp.longValue();
}
}
return source.getModificationStamp();
}
@Override
public Source[] getPrioritySources() {
return priorityOrder;
}
@Override
public Namespace getPublicNamespace(LibraryElement library) {
// TODO(brianwilkerson) Rename this to not start with 'get'. Note that this is not part of the
// API of the interface.
Source source = library.getDefiningCompilationUnit().getSource();
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
return null;
}
Namespace namespace = null;
if (dartEntry.getValue(DartEntry.ELEMENT) == library) {
namespace = dartEntry.getValue(DartEntry.PUBLIC_NAMESPACE);
}
if (namespace == null) {
NamespaceBuilder builder = new NamespaceBuilder();
namespace = builder.createPublicNamespaceForLibrary(library);
synchronized (cacheLock) {
dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
AnalysisEngine.getInstance().getLogger().logError(
"Could not compute the public namespace for " + library.getSource().getFullName(),
new AnalysisException("A Dart file became a non-Dart file: " + source.getFullName()));
return null;
}
if (dartEntry.getValue(DartEntry.ELEMENT) == library) {
DartEntryImpl dartCopy = getReadableDartEntry(source).getWritableCopy();
dartCopy.setValue(DartEntry.PUBLIC_NAMESPACE, namespace);
cache.put(source, dartCopy);
}
}
}
return namespace;
}
@Override
public Source[] getRefactoringUnsafeSources() {
ArrayList<Source> sources = new ArrayList<Source>();
synchronized (cacheLock) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry instanceof DartEntry) {
if (!((DartEntry) sourceEntry).isRefactoringSafe()) {
sources.add(iterator.getKey());
}
}
}
}
return sources.toArray(new Source[sources.size()]);
}
@Override
public CompilationUnit getResolvedCompilationUnit(Source unitSource, LibraryElement library) {
if (library == null) {
return null;
}
return getResolvedCompilationUnit(unitSource, library.getSource());
}
@Override
public CompilationUnit getResolvedCompilationUnit(Source unitSource, Source librarySource) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(unitSource);
if (sourceEntry instanceof DartEntry) {
return ((DartEntry) sourceEntry).getValueInLibrary(DartEntry.RESOLVED_UNIT, librarySource);
}
return null;
}
@Override
public HtmlUnit getResolvedHtmlUnit(Source htmlSource) {
SourceEntry sourceEntry = getReadableSourceEntryOrNull(htmlSource);
if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
return htmlEntry.getValue(HtmlEntry.RESOLVED_UNIT);
}
return null;
}
@Override
public SourceFactory getSourceFactory() {
return sourceFactory;
}
/**
* Return a list of the sources that would be processed by {@link #performAnalysisTask()}. This
* method duplicates, and must therefore be kept in sync with, {@link #getNextAnalysisTask()}.
* This method is intended to be used for testing purposes only.
*
* @return a list of the sources that would be processed by {@link #performAnalysisTask()}
*/
@VisibleForTesting
public List<Source> getSourcesNeedingProcessing() {
HashSet<Source> sources = new HashSet<Source>();
synchronized (cacheLock) {
boolean hintsEnabled = options.getHint();
//
// Look for priority sources that need to be analyzed.
//
for (Source source : priorityOrder) {
getSourcesNeedingProcessing(source, cache.get(source), true, hintsEnabled, sources);
}
//
// Look for non-priority sources that need to be analyzed.
//
WorkManager.WorkIterator iterator = workManager.iterator();
while (iterator.hasNext()) {
Source source = iterator.next();
getSourcesNeedingProcessing(source, cache.get(source), false, hintsEnabled, sources);
}
}
return new ArrayList<Source>(sources);
}
@Override
public AnalysisContextStatistics getStatistics() {
boolean hintsEnabled = options.getHint();
AnalysisContextStatisticsImpl statistics = new AnalysisContextStatisticsImpl();
synchronized (cacheLock) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry instanceof DartEntry) {
Source source = iterator.getKey();
DartEntry dartEntry = (DartEntry) sourceEntry;
SourceKind kind = dartEntry.getValue(DartEntry.SOURCE_KIND);
// get library independent values
statistics.putCacheItem(dartEntry, SourceEntry.LINE_INFO);
statistics.putCacheItem(dartEntry, DartEntry.PARSE_ERRORS);
statistics.putCacheItem(dartEntry, DartEntry.PARSED_UNIT);
statistics.putCacheItem(dartEntry, DartEntry.SOURCE_KIND);
if (kind == SourceKind.LIBRARY) {
statistics.putCacheItem(dartEntry, DartEntry.ELEMENT);
statistics.putCacheItem(dartEntry, DartEntry.EXPORTED_LIBRARIES);
statistics.putCacheItem(dartEntry, DartEntry.IMPORTED_LIBRARIES);
statistics.putCacheItem(dartEntry, DartEntry.INCLUDED_PARTS);
statistics.putCacheItem(dartEntry, DartEntry.IS_CLIENT);
statistics.putCacheItem(dartEntry, DartEntry.IS_LAUNCHABLE);
// The public namespace isn't computed by performAnalysisTask() and therefore isn't
// interesting.
//statistics.putCacheItem(dartEntry, DartEntry.PUBLIC_NAMESPACE);
}
// get library-specific values
Source[] librarySources = getLibrariesContaining(source);
for (Source librarySource : librarySources) {
statistics.putCacheItemInLibrary(dartEntry, librarySource, DartEntry.RESOLUTION_ERRORS);
statistics.putCacheItemInLibrary(dartEntry, librarySource, DartEntry.RESOLVED_UNIT);
if (generateSdkErrors || !source.isInSystemLibrary()) {
statistics.putCacheItemInLibrary(
dartEntry,
librarySource,
DartEntry.VERIFICATION_ERRORS);
if (hintsEnabled) {
statistics.putCacheItemInLibrary(dartEntry, librarySource, DartEntry.HINTS);
}
}
}
} else if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
statistics.putCacheItem(htmlEntry, SourceEntry.LINE_INFO);
statistics.putCacheItem(htmlEntry, HtmlEntry.PARSE_ERRORS);
statistics.putCacheItem(htmlEntry, HtmlEntry.PARSED_UNIT);
statistics.putCacheItem(htmlEntry, HtmlEntry.RESOLUTION_ERRORS);
statistics.putCacheItem(htmlEntry, HtmlEntry.RESOLVED_UNIT);
// We are not currently recording any hints related to HTML.
// statistics.putCacheItem(htmlEntry, HtmlEntry.HINTS);
}
}
}
statistics.setPartitionData(cache.getPartitionData());
return statistics;
}
@Override
public TypeProvider getTypeProvider() throws AnalysisException {
Source coreSource = getSourceFactory().forUri(DartSdk.DART_CORE);
if (coreSource == null) {
throw new AnalysisException("Could not create a source for dart:core");
}
LibraryElement coreElement = computeLibraryElement(coreSource);
if (coreElement == null) {
throw new AnalysisException("Could not create an element for dart:core");
}
return new TypeProviderImpl(coreElement);
}
@Override
public boolean isClientLibrary(Source librarySource) {
SourceEntry sourceEntry = getReadableSourceEntry(librarySource);
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
return dartEntry.getValue(DartEntry.IS_CLIENT) && dartEntry.getValue(DartEntry.IS_LAUNCHABLE);
}
return false;
}
@Override
public boolean isDisposed() {
return disposed;
}
@Override
public boolean isServerLibrary(Source librarySource) {
SourceEntry sourceEntry = getReadableSourceEntry(librarySource);
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
return !dartEntry.getValue(DartEntry.IS_CLIENT)
&& dartEntry.getValue(DartEntry.IS_LAUNCHABLE);
}
return false;
}
@Override
public void mergeContext(AnalysisContext context) {
if (context instanceof InstrumentedAnalysisContextImpl) {
context = ((InstrumentedAnalysisContextImpl) context).getBasis();
}
if (!(context instanceof AnalysisContextImpl)) {
return;
}
synchronized (cacheLock) {
// TODO(brianwilkerson) This does not lock against the other context's cacheLock.
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source newSource = iterator.getKey();
SourceEntry existingEntry = getReadableSourceEntry(newSource);
if (existingEntry == null) {
// TODO(brianwilkerson) Decide whether we really need to copy the info.
cache.put(newSource, iterator.getValue().getWritableCopy());
} else {
// TODO(brianwilkerson) Decide whether/how to merge the entries.
}
}
}
}
@Override
public CompilationUnit parseCompilationUnit(Source source) throws AnalysisException {
return getDartParseData(source, DartEntry.PARSED_UNIT, null);
}
@Override
public HtmlUnit parseHtmlUnit(Source source) throws AnalysisException {
return getHtmlParseData(source, HtmlEntry.PARSED_UNIT, null);
}
@Override
public AnalysisResult performAnalysisTask() {
if (TRACE_PERFORM_TASK) {
System.out.println("----------------------------------------");
}
long getStart = System.currentTimeMillis();
AnalysisTask task = getNextAnalysisTask();
long getEnd = System.currentTimeMillis();
if (task == null && validateCacheConsistency()) {
task = getNextAnalysisTask();
}
if (task == null) {
return new AnalysisResult(getChangeNotices(true), getEnd - getStart, null, -1L);
}
String taskDescription = task.toString();
notifyAboutToPerformTask(taskDescription);
if (TRACE_PERFORM_TASK) {
System.out.println(taskDescription);
}
long performStart = System.currentTimeMillis();
try {
task.perform(resultRecorder);
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not perform analysis task: " + taskDescription,
exception);
} catch (AnalysisException exception) {
if (!(exception.getCause() instanceof IOException)) {
AnalysisEngine.getInstance().getLogger().logError(
"Internal error while performing the task: " + task,
exception);
}
}
long performEnd = System.currentTimeMillis();
ChangeNotice[] notices = getChangeNotices(false);
int noticeCount = notices.length;
for (int i = 0; i < noticeCount; i++) {
ChangeNotice notice = notices[i];
Source source = notice.getSource();
// TODO(brianwilkerson) Figure out whether the compilation unit is always resolved, or whether
// we need to decide whether to invoke the "parsed" or "resolved" method. This might be better
// done when recording task results in order to reduce the chance of errors.
// if (notice.getCompilationUnit() != null) {
// notifyResolvedDart(source, notice.getCompilationUnit());
// } else if (notice.getHtmlUnit() != null) {
// notifyResolvedHtml(source, notice.getHtmlUnit());
// }
notifyErrors(source, notice.getErrors(), notice.getLineInfo());
}
return new AnalysisResult(notices, getEnd - getStart, task.getClass().getName(), performEnd
- performStart);
}
@Override
public void recordLibraryElements(Map<Source, LibraryElement> elementMap) {
synchronized (cacheLock) {
Source htmlSource = sourceFactory.forUri(DartSdk.DART_HTML);
for (Map.Entry<Source, LibraryElement> entry : elementMap.entrySet()) {
Source librarySource = entry.getKey();
LibraryElement library = entry.getValue();
//
// Cache the element in the library's info.
//
DartEntry dartEntry = getReadableDartEntry(librarySource);
if (dartEntry != null) {
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
recordElementData(dartCopy, library, library.getSource(), htmlSource);
dartCopy.setState(SourceEntry.CONTENT, CacheState.FLUSHED);
dartCopy.setValue(SourceEntry.LINE_INFO, new LineInfo(new int[] {0}));
dartCopy.setValue(DartEntry.ANGULAR_ERRORS, AnalysisError.NO_ERRORS);
// DartEntry.ELEMENT - set in recordElementData
dartCopy.setValue(DartEntry.EXPORTED_LIBRARIES, Source.EMPTY_ARRAY);
dartCopy.setValue(DartEntry.IMPORTED_LIBRARIES, Source.EMPTY_ARRAY);
dartCopy.setValue(DartEntry.INCLUDED_PARTS, Source.EMPTY_ARRAY);
// DartEntry.IS_CLIENT - set in recordElementData
// DartEntry.IS_LAUNCHABLE - set in recordElementData
dartCopy.setValue(DartEntry.PARSE_ERRORS, AnalysisError.NO_ERRORS);
dartCopy.setState(DartEntry.PARSED_UNIT, CacheState.FLUSHED);
dartCopy.setState(DartEntry.PUBLIC_NAMESPACE, CacheState.FLUSHED);
dartCopy.setValue(DartEntry.SCAN_ERRORS, AnalysisError.NO_ERRORS);
dartCopy.setValue(DartEntry.SOURCE_KIND, SourceKind.LIBRARY);
dartCopy.setState(DartEntry.TOKEN_STREAM, CacheState.FLUSHED);
dartCopy.setValueInLibrary(
DartEntry.RESOLUTION_ERRORS,
librarySource,
AnalysisError.NO_ERRORS);
dartCopy.setStateInLibrary(DartEntry.RESOLVED_UNIT, librarySource, CacheState.FLUSHED);
dartCopy.setValueInLibrary(
DartEntry.VERIFICATION_ERRORS,
librarySource,
AnalysisError.NO_ERRORS);
dartCopy.setValueInLibrary(DartEntry.HINTS, librarySource, AnalysisError.NO_ERRORS);
cache.put(librarySource, dartCopy);
}
}
}
}
@Override
public void removeListener(AnalysisListener listener) {
listeners.remove(listener);
}
@Override
public CompilationUnit resolveCompilationUnit(Source unitSource, LibraryElement library)
throws AnalysisException {
if (library == null) {
return null;
}
return resolveCompilationUnit(unitSource, library.getSource());
}
@Override
public CompilationUnit resolveCompilationUnit(Source unitSource, Source librarySource)
throws AnalysisException {
return getDartResolutionData(unitSource, librarySource, DartEntry.RESOLVED_UNIT, null);
}
@Override
public HtmlUnit resolveHtmlUnit(Source htmlSource) throws AnalysisException {
computeHtmlElement(htmlSource);
return parseHtmlUnit(htmlSource);
}
@Override
public void setAnalysisOptions(AnalysisOptions options) {
synchronized (cacheLock) {
boolean needsRecompute = this.options.getAnalyzeAngular() != options.getAnalyzeAngular()
|| this.options.getAnalyzeFunctionBodies() != options.getAnalyzeFunctionBodies()
|| this.options.getGenerateSdkErrors() != options.getGenerateSdkErrors()
|| this.options.getEnableAsync() != options.getEnableAsync()
|| this.options.getEnableDeferredLoading() != options.getEnableDeferredLoading()
|| this.options.getEnableEnum() != options.getEnableEnum()
|| this.options.getDart2jsHint() != options.getDart2jsHint()
|| (this.options.getHint() && !options.getHint())
|| this.options.getPreserveComments() != options.getPreserveComments();
int cacheSize = options.getCacheSize();
if (this.options.getCacheSize() != cacheSize) {
this.options.setCacheSize(cacheSize);
//cache.setMaxCacheSize(cacheSize);
privatePartition.setMaxCacheSize(cacheSize);
//
// Cap the size of the priority list to being less than the cache size. Failure to do so can
// result in an infinite loop in performAnalysisTask() because re-caching one AST structure
// can cause another priority source's AST structure to be flushed.
//
int maxPriorityOrderSize = cacheSize - PRIORITY_ORDER_SIZE_DELTA;
if (priorityOrder.length > maxPriorityOrderSize) {
Source[] newPriorityOrder = new Source[maxPriorityOrderSize];
System.arraycopy(priorityOrder, 0, newPriorityOrder, 0, maxPriorityOrderSize);
priorityOrder = newPriorityOrder;
}
}
this.options.setAnalyzeAngular(options.getAnalyzeAngular());
this.options.setAnalyzeFunctionBodies(options.getAnalyzeFunctionBodies());
this.options.setGenerateSdkErrors(options.getGenerateSdkErrors());
this.options.setEnableAsync(options.getEnableAsync());
this.options.setEnableDeferredLoading(options.getEnableDeferredLoading());
this.options.setEnableEnum(options.getEnableEnum());
this.options.setDart2jsHint(options.getDart2jsHint());
this.options.setHint(options.getHint());
this.options.setIncremental(options.getIncremental());
this.options.setPreserveComments(options.getPreserveComments());
generateSdkErrors = options.getGenerateSdkErrors();
if (needsRecompute) {
invalidateAllLocalResolutionInformation(false);
}
}
}
@Override
public void setAnalysisPriorityOrder(List<Source> sources) {
synchronized (cacheLock) {
if (sources == null || sources.isEmpty()) {
priorityOrder = Source.EMPTY_ARRAY;
} else {
while (sources.remove(null)) {
// Nothing else to do.
}
if (sources.isEmpty()) {
priorityOrder = Source.EMPTY_ARRAY;
}
//
// Cap the size of the priority list to being less than the cache size. Failure to do so can
// result in an infinite loop in performAnalysisTask() because re-caching one AST structure
// can cause another priority source's AST structure to be flushed.
//
int count = Math.min(sources.size(), options.getCacheSize() - PRIORITY_ORDER_SIZE_DELTA);
priorityOrder = new Source[count];
for (int i = 0; i < count; i++) {
priorityOrder[i] = sources.get(i);
}
}
}
}
@Override
public void setChangedContents(Source source, String contents, int offset, int oldLength,
int newLength) {
synchronized (cacheLock) {
String originalContents = contentCache.setContents(source, contents);
if (contents != null) {
if (!contents.equals(originalContents)) {
if (options.getIncremental()) {
incrementalAnalysisCache = IncrementalAnalysisCache.update(
incrementalAnalysisCache,
source,
originalContents,
contents,
offset,
oldLength,
newLength,
getReadableSourceEntry(source));
}
sourceChanged(source);
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry != null) {
SourceEntryImpl sourceCopy = sourceEntry.getWritableCopy();
sourceCopy.setModificationTime(contentCache.getModificationStamp(source));
sourceCopy.setValue(SourceEntry.CONTENT, contents);
cache.put(source, sourceCopy);
}
}
} else if (originalContents != null) {
incrementalAnalysisCache = IncrementalAnalysisCache.clear(incrementalAnalysisCache, source);
sourceChanged(source);
}
}
}
@Override
public void setContents(Source source, String contents) {
synchronized (cacheLock) {
String originalContents = contentCache.setContents(source, contents);
if (contents != null) {
if (!contents.equals(originalContents)) {
incrementalAnalysisCache = IncrementalAnalysisCache.clear(
incrementalAnalysisCache,
source);
sourceChanged(source);
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry != null) {
SourceEntryImpl sourceCopy = sourceEntry.getWritableCopy();
sourceCopy.setModificationTime(contentCache.getModificationStamp(source));
sourceCopy.setValue(SourceEntry.CONTENT, contents);
cache.put(source, sourceCopy);
}
}
} else if (originalContents != null) {
incrementalAnalysisCache = IncrementalAnalysisCache.clear(incrementalAnalysisCache, source);
sourceChanged(source);
}
}
}
@Override
public void setSourceFactory(SourceFactory factory) {
synchronized (cacheLock) {
if (sourceFactory == factory) {
return;
} else if (factory.getContext() != null) {
throw new IllegalStateException("Source factories cannot be shared between contexts");
}
if (sourceFactory != null) {
sourceFactory.setContext(null);
}
factory.setContext(this);
sourceFactory = factory;
coreLibrarySource = sourceFactory.forUri(DartSdk.DART_CORE);
cache = createCacheFromSourceFactory(factory);
invalidateAllLocalResolutionInformation(true);
}
}
/**
* Create an analysis cache based on the given source factory.
*
* @param factory the source factory containing the information needed to create the cache
* @return the cache that was created
*/
protected AnalysisCache createCacheFromSourceFactory(SourceFactory factory) {
if (factory == null) {
return new AnalysisCache(new CachePartition[] {privatePartition});
}
DartSdk sdk = factory.getDartSdk();
if (sdk == null) {
return new AnalysisCache(new CachePartition[] {privatePartition});
}
return new AnalysisCache(new CachePartition[] {
AnalysisEngine.getInstance().getPartitionManager().forSdk(sdk), privatePartition});
}
/**
* Record the results produced by performing a {@link ResolveDartLibraryCycleTask}. If the results
* were computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
protected DartEntry recordResolveDartLibraryCycleTaskResults(ResolveDartLibraryCycleTask task)
throws AnalysisException {
LibraryResolver2 resolver = task.getLibraryResolver();
AnalysisException thrownException = task.getException();
DartEntry unitEntry = null;
Source unitSource = task.getUnitSource();
if (resolver != null) {
//
// The resolver should only be null if an exception was thrown before (or while) it was
// being created.
//
List<ResolvableLibrary> resolvedLibraries = resolver.getResolvedLibraries();
synchronized (cacheLock) {
if (resolvedLibraries == null) {
//
// The resolved libraries should only be null if an exception was thrown during resolution.
//
unitEntry = getReadableDartEntry(unitSource);
if (unitEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ unitSource.getFullName());
}
DartEntryImpl dartCopy = unitEntry.getWritableCopy();
if (thrownException == null) {
dartCopy.recordResolutionError(new AnalysisException(
"In recordResolveDartLibraryCycleTaskResults, resolvedLibraries was null and there was no thrown exception"));
} else {
dartCopy.recordResolutionError(thrownException);
}
cache.put(unitSource, dartCopy);
cache.remove(unitSource);
if (thrownException != null) {
throw thrownException;
}
return dartCopy;
}
if (allModificationTimesMatch(resolvedLibraries)) {
Source htmlSource = getSourceFactory().forUri(DartSdk.DART_HTML);
RecordingErrorListener errorListener = resolver.getErrorListener();
for (ResolvableLibrary library : resolvedLibraries) {
Source librarySource = library.getLibrarySource();
for (Source source : library.getCompilationUnitSources()) {
CompilationUnit unit = library.getAST(source);
AnalysisError[] errors = errorListener.getErrorsForSource(source);
LineInfo lineInfo = getLineInfo(source);
DartEntryImpl dartCopy = (DartEntryImpl) cache.get(source).getWritableCopy();
if (thrownException == null) {
dartCopy.setState(DartEntry.PARSED_UNIT, CacheState.FLUSHED);
dartCopy.setValueInLibrary(DartEntry.RESOLVED_UNIT, librarySource, unit);
dartCopy.setValueInLibrary(DartEntry.RESOLUTION_ERRORS, librarySource, errors);
if (source.equals(librarySource)) {
recordElementData(
dartCopy,
library.getLibraryElement(),
librarySource,
htmlSource);
}
cache.storedAst(source);
} else {
dartCopy.recordResolutionErrorInLibrary(librarySource, thrownException);
cache.remove(source);
}
cache.put(source, dartCopy);
if (!source.equals(librarySource)) {
workManager.add(source, SourcePriority.PRIORITY_PART);
}
if (source.equals(unitSource)) {
unitEntry = dartCopy;
}
ChangeNoticeImpl notice = getNotice(source);
notice.setCompilationUnit(unit);
notice.setErrors(dartCopy.getAllErrors(), lineInfo);
}
}
} else {
@SuppressWarnings("resource")
PrintStringWriter writer = new PrintStringWriter();
writer.println("Library resolution results discarded for");
for (ResolvableLibrary library : resolvedLibraries) {
for (Source source : library.getCompilationUnitSources()) {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry != null) {
long resultTime = library.getModificationTime(source);
writer.println(" " + debuggingString(source) + "; sourceTime = "
+ getModificationStamp(source) + ", resultTime = " + resultTime
+ ", cacheTime = " + dartEntry.getModificationTime());
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the
// sources will be re-analyzed using the up-to-date sources.
//
dartCopy.recordResolutionNotInProcess();
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark
// the cache so that we won't attempt to re-analyze the sources until there's a
// good chance that we'll be able to do so without error.
//
dartCopy.recordResolutionError(thrownException);
cache.remove(source);
}
cache.put(source, dartCopy);
if (source.equals(unitSource)) {
unitEntry = dartCopy;
}
} else {
writer.println(" " + debuggingString(source) + "; sourceTime = "
+ getModificationStamp(source) + ", no entry");
}
}
}
logInformation(writer.toString());
}
}
}
if (thrownException != null) {
throw thrownException;
}
if (unitEntry == null) {
unitEntry = getReadableDartEntry(unitSource);
if (unitEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ unitSource.getFullName());
}
}
return unitEntry;
}
/*
* Record the results produced by performing a {@link ResolveDartLibraryTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
protected DartEntry recordResolveDartLibraryTaskResults(ResolveDartLibraryTask task)
throws AnalysisException {
LibraryResolver resolver = task.getLibraryResolver();
AnalysisException thrownException = task.getException();
DartEntry unitEntry = null;
Source unitSource = task.getUnitSource();
if (resolver != null) {
//
// The resolver should only be null if an exception was thrown before (or while) it was
// being created.
//
Set<Library> resolvedLibraries = resolver.getResolvedLibraries();
synchronized (cacheLock) {
if (resolvedLibraries == null) {
//
// The resolved libraries should only be null if an exception was thrown during resolution.
//
unitEntry = getReadableDartEntry(unitSource);
if (unitEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ unitSource.getFullName());
}
DartEntryImpl dartCopy = unitEntry.getWritableCopy();
if (thrownException == null) {
dartCopy.recordResolutionError(new AnalysisException(
"In recordResolveDartLibraryTaskResults, resolvedLibraries was null and there was no thrown exception"));
} else {
dartCopy.recordResolutionError(thrownException);
}
cache.put(unitSource, dartCopy);
cache.remove(unitSource);
if (thrownException != null) {
throw thrownException;
}
return dartCopy;
}
if (allModificationTimesMatch(resolvedLibraries)) {
Source htmlSource = getSourceFactory().forUri(DartSdk.DART_HTML);
RecordingErrorListener errorListener = resolver.getErrorListener();
for (Library library : resolvedLibraries) {
Source librarySource = library.getLibrarySource();
for (Source source : library.getCompilationUnitSources()) {
CompilationUnit unit = library.getAST(source);
AnalysisError[] errors = errorListener.getErrorsForSource(source);
LineInfo lineInfo = getLineInfo(source);
DartEntry dartEntry = (DartEntry) cache.get(source);
long sourceTime = getModificationStamp(source);
if (dartEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ source.getFullName());
}
}
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null) {
dartCopy.setValue(SourceEntry.LINE_INFO, lineInfo);
dartCopy.setState(DartEntry.PARSED_UNIT, CacheState.FLUSHED);
dartCopy.setValueInLibrary(DartEntry.RESOLVED_UNIT, librarySource, unit);
dartCopy.setValueInLibrary(DartEntry.RESOLUTION_ERRORS, librarySource, errors);
if (source.equals(librarySource)) {
recordElementData(
dartCopy,
library.getLibraryElement(),
librarySource,
htmlSource);
}
cache.storedAst(source);
} else {
dartCopy.recordResolutionErrorInLibrary(librarySource, thrownException);
cache.remove(source);
}
cache.put(source, dartCopy);
if (!source.equals(librarySource)) {
workManager.add(source, SourcePriority.PRIORITY_PART);
}
if (source.equals(unitSource)) {
unitEntry = dartCopy;
}
ChangeNoticeImpl notice = getNotice(source);
notice.setCompilationUnit(unit);
notice.setErrors(dartCopy.getAllErrors(), lineInfo);
}
}
} else {
@SuppressWarnings("resource")
PrintStringWriter writer = new PrintStringWriter();
writer.println("Library resolution results discarded for");
for (Library library : resolvedLibraries) {
for (Source source : library.getCompilationUnitSources()) {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry != null) {
long resultTime = library.getModificationTime(source);
writer.println(" " + debuggingString(source) + "; sourceTime = "
+ getModificationStamp(source) + ", resultTime = " + resultTime
+ ", cacheTime = " + dartEntry.getModificationTime());
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the
// sources will be re-analyzed using the up-to-date sources.
//
dartCopy.recordResolutionNotInProcess();
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark
// the cache so that we won't attempt to re-analyze the sources until there's a
// good chance that we'll be able to do so without error.
//
dartCopy.recordResolutionError(thrownException);
cache.remove(source);
}
cache.put(source, dartCopy);
if (source.equals(unitSource)) {
unitEntry = dartCopy;
}
} else {
writer.println(" " + debuggingString(source) + "; sourceTime = "
+ getModificationStamp(source) + ", no entry");
}
}
}
logInformation(writer.toString());
}
}
}
if (thrownException != null) {
throw thrownException;
}
if (unitEntry == null) {
unitEntry = getReadableDartEntry(unitSource);
if (unitEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ unitSource.getFullName());
}
}
return unitEntry;
}
/**
* Record that we have accessed the AST structure associated with the given source. At the moment,
* there is no differentiation between the parsed and resolved forms of the AST.
*
* @param source the source whose AST structure was accessed
*/
private void accessedAst(Source source) {
synchronized (cacheLock) {
cache.accessedAst(source);
}
}
/**
* Add all of the sources contained in the given source container to the given list of sources.
* <p>
* Note: This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param sources the list to which sources are to be added
* @param container the source container containing the sources to be added to the list
*/
private void addSourcesInContainer(ArrayList<Source> sources, SourceContainer container) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
if (container.contains(source)) {
sources.add(source);
}
}
}
/**
* Return {@code true} if the modification times of the sources used by the given library resolver
* to resolve one or more libraries are consistent with the modification times in the cache.
*
* @param resolver the library resolver used to resolve one or more libraries
* @return {@code true} if we should record the results of the resolution
* @throws AnalysisException if any of the modification times could not be determined (this should
* not happen)
*/
private boolean allModificationTimesMatch(List<ResolvableLibrary> resolvedLibraries)
throws AnalysisException {
boolean allTimesMatch = true;
for (ResolvableLibrary library : resolvedLibraries) {
for (Source source : library.getCompilationUnitSources()) {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
// This shouldn't be possible because we should never have performed the task if the
// source didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-Dart file as a Dart file: "
+ source.getFullName());
}
long sourceTime = getModificationStamp(source);
long resultTime = library.getModificationTime(source);
if (sourceTime != resultTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
allTimesMatch = false;
}
}
}
return allTimesMatch;
}
/**
* Return {@code true} if the modification times of the sources used by the given library resolver
* to resolve one or more libraries are consistent with the modification times in the cache.
*
* @param resolver the library resolver used to resolve one or more libraries
* @return {@code true} if we should record the results of the resolution
* @throws AnalysisException if any of the modification times could not be determined (this should
* not happen)
*/
private boolean allModificationTimesMatch(Set<Library> resolvedLibraries)
throws AnalysisException {
boolean allTimesMatch = true;
for (Library library : resolvedLibraries) {
for (Source source : library.getCompilationUnitSources()) {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
// This shouldn't be possible because we should never have performed the task if the
// source didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-Dart file as a Dart file: "
+ source.getFullName());
}
long sourceTime = getModificationStamp(source);
long resultTime = library.getModificationTime(source);
if (sourceTime != resultTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
allTimesMatch = false;
}
}
}
return allTimesMatch;
}
/**
* Given a source for a Dart file and the library that contains it, return a cache entry in which
* the state of the data represented by the given descriptor is either {@link CacheState#VALID} or
* {@link CacheState#ERROR}. This method assumes that the data can be produced by generating hints
* for the library if the data is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
private DartEntry cacheDartHintData(Source unitSource, Source librarySource, DartEntry dartEntry,
DataDescriptor<?> descriptor) throws AnalysisException {
//
// Check to see whether we already have the information being requested.
//
CacheState state = dartEntry.getStateInLibrary(descriptor, librarySource);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
DartEntry libraryEntry = getReadableDartEntry(librarySource);
libraryEntry = cacheDartResolutionData(
librarySource,
librarySource,
libraryEntry,
DartEntry.ELEMENT);
LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
CompilationUnitElement definingUnit = libraryElement.getDefiningCompilationUnit();
CompilationUnitElement[] parts = libraryElement.getParts();
@SuppressWarnings("unchecked")
TimestampedData<CompilationUnit>[] units = new TimestampedData[parts.length + 1];
units[0] = getResolvedUnit(definingUnit, librarySource);
if (units[0] == null) {
Source source = definingUnit.getSource();
units[0] = new TimestampedData<CompilationUnit>(
getModificationStamp(source),
resolveCompilationUnit(source, libraryElement));
}
for (int i = 0; i < parts.length; i++) {
units[i + 1] = getResolvedUnit(parts[i], librarySource);
if (units[i + 1] == null) {
Source source = parts[i].getSource();
units[i + 1] = new TimestampedData<CompilationUnit>(
getModificationStamp(source),
resolveCompilationUnit(source, libraryElement));
}
}
dartEntry = (DartEntry) new GenerateDartHintsTask(
this,
units,
getLibraryElement(librarySource)).perform(resultRecorder);
state = dartEntry.getStateInLibrary(descriptor, librarySource);
}
return dartEntry;
}
/**
* Given a source for a Dart file, return a cache entry in which the state of the data represented
* by the given descriptor is either {@link CacheState#VALID} or {@link CacheState#ERROR}. This
* method assumes that the data can be produced by parsing the source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
private DartEntry cacheDartParseData(Source source, DartEntry dartEntry,
DataDescriptor<?> descriptor) throws AnalysisException {
if (descriptor == DartEntry.PARSED_UNIT) {
if (dartEntry.hasResolvableCompilationUnit()) {
return dartEntry;
}
}
//
// Check to see whether we already have the information being requested.
//
CacheState state = dartEntry.getState(descriptor);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
dartEntry = cacheDartScanData(source, dartEntry, DartEntry.TOKEN_STREAM);
dartEntry = (DartEntry) new ParseDartTask(
this,
source,
dartEntry.getModificationTime(),
dartEntry.getValue(DartEntry.TOKEN_STREAM),
dartEntry.getValue(SourceEntry.LINE_INFO)).perform(resultRecorder);
state = dartEntry.getState(descriptor);
}
return dartEntry;
}
/**
* Given a source for a Dart file and the library that contains it, return a cache entry in which
* the state of the data represented by the given descriptor is either {@link CacheState#VALID} or
* {@link CacheState#ERROR}. This method assumes that the data can be produced by resolving the
* source in the context of the library if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
private DartEntry cacheDartResolutionData(Source unitSource, Source librarySource,
DartEntry dartEntry, DataDescriptor<?> descriptor) throws AnalysisException {
//
// Check to see whether we already have the information being requested.
//
CacheState state = (descriptor == DartEntry.ELEMENT) ? dartEntry.getState(descriptor)
: dartEntry.getStateInLibrary(descriptor, librarySource);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
// TODO(brianwilkerson) As an optimization, if we already have the element model for the
// library we can use ResolveDartUnitTask to produce the resolved AST structure much faster.
dartEntry = (DartEntry) new ResolveDartLibraryTask(this, unitSource, librarySource).perform(resultRecorder);
state = (descriptor == DartEntry.ELEMENT) ? dartEntry.getState(descriptor)
: dartEntry.getStateInLibrary(descriptor, librarySource);
}
return dartEntry;
}
/**
* Given a source for a Dart file, return a cache entry in which the state of the data represented
* by the given descriptor is either {@link CacheState#VALID} or {@link CacheState#ERROR}. This
* method assumes that the data can be produced by scanning the source if it is not already
* cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be scanned
*/
private DartEntry cacheDartScanData(Source source, DartEntry dartEntry,
DataDescriptor<?> descriptor) throws AnalysisException {
//
// Check to see whether we already have the information being requested.
//
CacheState state = dartEntry.getState(descriptor);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
try {
if (dartEntry.getState(SourceEntry.CONTENT) != CacheState.VALID) {
dartEntry = (DartEntry) new GetContentTask(this, source).perform(resultRecorder);
}
dartEntry = (DartEntry) new ScanDartTask(
this,
source,
dartEntry.getModificationTime(),
dartEntry.getValue(SourceEntry.CONTENT)).perform(resultRecorder);
} catch (AnalysisException exception) {
throw exception;
} catch (Exception exception) {
throw new AnalysisException("Exception", exception);
}
state = dartEntry.getState(descriptor);
}
return dartEntry;
}
/**
* Given a source for a Dart file and the library that contains it, return a cache entry in which
* the state of the data represented by the given descriptor is either {@link CacheState#VALID} or
* {@link CacheState#ERROR}. This method assumes that the data can be produced by verifying the
* source in the given library if the data is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
private DartEntry cacheDartVerificationData(Source unitSource, Source librarySource,
DartEntry dartEntry, DataDescriptor<?> descriptor) throws AnalysisException {
//
// Check to see whether we already have the information being requested.
//
CacheState state = dartEntry.getStateInLibrary(descriptor, librarySource);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
LibraryElement library = computeLibraryElement(librarySource);
CompilationUnit unit = resolveCompilationUnit(unitSource, library);
if (unit == null) {
throw new AnalysisException("Could not resolve compilation unit "
+ unitSource.getFullName() + " in " + librarySource.getFullName());
}
dartEntry = (DartEntry) new GenerateDartErrorsTask(
this,
unitSource,
dartEntry.getModificationTime(),
unit,
library).perform(resultRecorder);
state = dartEntry.getStateInLibrary(descriptor, librarySource);
}
return dartEntry;
}
/**
* Given a source for an HTML file, return a cache entry in which all of the data represented by
* the state of the given descriptors is either {@link CacheState#VALID} or
* {@link CacheState#ERROR}. This method assumes that the data can be produced by parsing the
* source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the HTML file
* @param htmlEntry the cache entry associated with the HTML file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private HtmlEntry cacheHtmlParseData(Source source, HtmlEntry htmlEntry,
DataDescriptor<?> descriptor) throws AnalysisException {
if (descriptor == HtmlEntry.PARSED_UNIT) {
HtmlUnit unit = htmlEntry.getAnyParsedUnit();
if (unit != null) {
return htmlEntry;
}
}
//
// Check to see whether we already have the information being requested.
//
CacheState state = htmlEntry.getState(descriptor);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
try {
if (htmlEntry.getState(SourceEntry.CONTENT) != CacheState.VALID) {
htmlEntry = (HtmlEntry) new GetContentTask(this, source).perform(resultRecorder);
}
htmlEntry = (HtmlEntry) new ParseHtmlTask(
this,
source,
htmlEntry.getModificationTime(),
htmlEntry.getValue(SourceEntry.CONTENT)).perform(resultRecorder);
} catch (AnalysisException exception) {
throw exception;
} catch (Exception exception) {
throw new AnalysisException("Exception", exception);
}
state = htmlEntry.getState(descriptor);
}
return htmlEntry;
}
/**
* Given a source for an HTML file, return a cache entry in which the state of the data
* represented by the given descriptor is either {@link CacheState#VALID} or
* {@link CacheState#ERROR}. This method assumes that the data can be produced by resolving the
* source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the HTML file
* @param dartEntry the cache entry associated with the HTML file
* @param descriptor the descriptor representing the data to be returned
* @return a cache entry containing the required data
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private HtmlEntry cacheHtmlResolutionData(Source source, HtmlEntry htmlEntry,
DataDescriptor<?> descriptor) throws AnalysisException {
//
// Check to see whether we already have the information being requested.
//
CacheState state = htmlEntry.getState(descriptor);
while (state != CacheState.ERROR && state != CacheState.VALID) {
//
// If not, compute the information. Unless the modification date of the source continues to
// change, this loop will eventually terminate.
//
htmlEntry = cacheHtmlParseData(source, htmlEntry, HtmlEntry.PARSED_UNIT);
htmlEntry = (HtmlEntry) new ResolveHtmlTask(
this,
source,
htmlEntry.getModificationTime(),
htmlEntry.getValue(HtmlEntry.PARSED_UNIT)).perform(resultRecorder);
state = htmlEntry.getState(descriptor);
}
return htmlEntry;
}
/**
* Compute the transitive closure of all libraries that depend on the given library by adding such
* libraries to the given collection.
*
* @param library the library on which the other libraries depend
* @param librariesToInvalidate the libraries that depend on the given library
*/
private void computeAllLibrariesDependingOn(Source library, HashSet<Source> librariesToInvalidate) {
if (librariesToInvalidate.add(library)) {
for (Source dependentLibrary : getLibrariesDependingOn(library)) {
computeAllLibrariesDependingOn(dependentLibrary, librariesToInvalidate);
}
}
}
/**
* Compute the priority that should be used when the source associated with the given entry is
* added to the work manager.
*
* @param dartEntry the entry associated with the source
* @return the priority that was computed
*/
private SourcePriority computePriority(DartEntry dartEntry) {
SourceKind kind = dartEntry.getKind();
if (kind == SourceKind.LIBRARY) {
return SourcePriority.LIBRARY;
} else if (kind == SourceKind.PART) {
return SourcePriority.NORMAL_PART;
}
return SourcePriority.UNKNOWN;
}
/**
* Given the encoded form of a source, use the source factory to reconstitute the original source.
*
* @param encoding the encoded form of a source
* @return the source represented by the encoding
*/
private Source computeSourceFromEncoding(String encoding) {
synchronized (cacheLock) {
return sourceFactory.fromEncoding(encoding);
}
}
/**
* Return {@code true} if the given array of sources contains the given source.
*
* @param sources the sources being searched
* @param targetSource the source being searched for
* @return {@code true} if the given source is in the array
*/
private boolean contains(Source[] sources, Source targetSource) {
for (Source source : sources) {
if (source.equals(targetSource)) {
return true;
}
}
return false;
}
/**
* Return {@code true} if the given array of sources contains any of the given target sources.
*
* @param sources the sources being searched
* @param targetSources the sources being searched for
* @return {@code true} if any of the given target sources are in the array
*/
private boolean containsAny(Source[] sources, Source[] targetSources) {
for (Source targetSource : targetSources) {
if (contains(sources, targetSource)) {
return true;
}
}
return false;
}
/**
* Create a {@link GenerateDartErrorsTask} for the given source, marking the verification errors
* as being in-process. The compilation unit and the library can be the same if the compilation
* unit is the defining compilation unit of the library.
*
* @param unitSource the source for the compilation unit to be verified
* @param unitEntry the entry for the compilation unit
* @param librarySource the source for the library containing the compilation unit
* @param libraryEntry the entry for the library
* @return task data representing the created task
*/
private TaskData createGenerateDartErrorsTask(Source unitSource, DartEntry unitEntry,
Source librarySource, DartEntry libraryEntry) {
if (unitEntry.getStateInLibrary(DartEntry.RESOLVED_UNIT, librarySource) != CacheState.VALID
|| libraryEntry.getState(DartEntry.ELEMENT) != CacheState.VALID) {
return createResolveDartLibraryTask(librarySource, libraryEntry);
}
CompilationUnit unit = unitEntry.getValueInLibrary(DartEntry.RESOLVED_UNIT, librarySource);
if (unit == null) {
AnalysisException exception = new AnalysisException(
"Entry has VALID state for RESOLVED_UNIT but null value for " + unitSource.getFullName()
+ " in " + librarySource.getFullName());
AnalysisEngine.getInstance().getLogger().logInformation(exception.getMessage(), exception);
DartEntryImpl dartCopy = unitEntry.getWritableCopy();
dartCopy.recordResolutionError(exception);
cache.put(unitSource, dartCopy);
return new TaskData(null, false);
}
LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
DartEntryImpl dartCopy = unitEntry.getWritableCopy();
dartCopy.setStateInLibrary(DartEntry.VERIFICATION_ERRORS, librarySource, CacheState.IN_PROCESS);
cache.put(unitSource, dartCopy);
return new TaskData(new GenerateDartErrorsTask(
this,
unitSource,
dartCopy.getModificationTime(),
unit,
libraryElement), false);
}
/**
* Create a {@link GenerateDartHintsTask} for the given source, marking the hints as being
* in-process.
*
* @param source the source whose content is to be verified
* @param dartEntry the entry for the source
* @param librarySource the source for the library containing the source
* @param libraryEntry the entry for the library
* @return task data representing the created task
*/
private TaskData createGenerateDartHintsTask(Source source, DartEntry dartEntry,
Source librarySource, DartEntry libraryEntry) {
if (libraryEntry.getState(DartEntry.ELEMENT) != CacheState.VALID) {
return createResolveDartLibraryTask(librarySource, libraryEntry);
}
LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
CompilationUnitElement definingUnit = libraryElement.getDefiningCompilationUnit();
CompilationUnitElement[] parts = libraryElement.getParts();
@SuppressWarnings("unchecked")
TimestampedData<CompilationUnit>[] units = new TimestampedData[parts.length + 1];
units[0] = getResolvedUnit(definingUnit, librarySource);
if (units[0] == null) {
// TODO(brianwilkerson) We should return a ResolveDartUnitTask (unless there are multiple ASTs
// that need to be resolved.
return createResolveDartLibraryTask(librarySource, libraryEntry);
}
for (int i = 0; i < parts.length; i++) {
units[i + 1] = getResolvedUnit(parts[i], librarySource);
if (units[i + 1] == null) {
// TODO(brianwilkerson) We should return a ResolveDartUnitTask (unless there are multiple
// ASTs that need to be resolved.
return createResolveDartLibraryTask(librarySource, libraryEntry);
}
}
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.setStateInLibrary(DartEntry.HINTS, librarySource, CacheState.IN_PROCESS);
cache.put(source, dartCopy);
return new TaskData(new GenerateDartHintsTask(this, units, libraryElement), false);
}
/**
* Create a {@link GetContentTask} for the given source, marking the content as being in-process.
*
* @param source the source whose content is to be accessed
* @param sourceEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createGetContentTask(Source source, SourceEntry sourceEntry) {
SourceEntryImpl sourceCopy = sourceEntry.getWritableCopy();
sourceCopy.setState(SourceEntry.CONTENT, CacheState.IN_PROCESS);
cache.put(source, sourceCopy);
return new TaskData(new GetContentTask(this, source), false);
}
/**
* Create a {@link ParseDartTask} for the given source, marking the parse errors as being
* in-process.
*
* @param source the source whose content is to be parsed
* @param dartEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createParseDartTask(Source source, DartEntry dartEntry) {
if (dartEntry.getState(DartEntry.TOKEN_STREAM) != CacheState.VALID
|| dartEntry.getState(SourceEntry.LINE_INFO) != CacheState.VALID) {
return createScanDartTask(source, dartEntry);
}
Token tokenStream = dartEntry.getValue(DartEntry.TOKEN_STREAM);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.setState(DartEntry.TOKEN_STREAM, CacheState.FLUSHED);
dartCopy.setState(DartEntry.PARSE_ERRORS, CacheState.IN_PROCESS);
cache.put(source, dartCopy);
return new TaskData(new ParseDartTask(
this,
source,
dartCopy.getModificationTime(),
tokenStream,
dartEntry.getValue(SourceEntry.LINE_INFO)), false);
}
/**
* Create a {@link ParseHtmlTask} for the given source, marking the parse errors as being
* in-process.
*
* @param source the source whose content is to be parsed
* @param htmlEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createParseHtmlTask(Source source, HtmlEntry htmlEntry) {
if (htmlEntry.getState(SourceEntry.CONTENT) != CacheState.VALID) {
return createGetContentTask(source, htmlEntry);
}
CharSequence content = htmlEntry.getValue(SourceEntry.CONTENT);
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setState(SourceEntry.CONTENT, CacheState.FLUSHED);
htmlCopy.setState(HtmlEntry.PARSE_ERRORS, CacheState.IN_PROCESS);
cache.put(source, htmlCopy);
return new TaskData(
new ParseHtmlTask(this, source, htmlCopy.getModificationTime(), content),
false);
}
/**
* Create a {@link PolymerBuildHtmlTask} for the given source, marking the Polymer elements as
* being in-process.
*
* @param source the source whose content is to be processed
* @param htmlEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createPolymerBuildHtmlTask(Source source, HtmlEntry htmlEntry) {
if (htmlEntry.getState(HtmlEntry.RESOLVED_UNIT) != CacheState.VALID) {
return createResolveHtmlTask(source, htmlEntry);
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setState(HtmlEntry.POLYMER_BUILD_ERRORS, CacheState.IN_PROCESS);
cache.put(source, htmlCopy);
return new TaskData(new PolymerBuildHtmlTask(
this,
source,
htmlCopy.getModificationTime(),
htmlEntry.getValue(SourceEntry.LINE_INFO),
htmlCopy.getValue(HtmlEntry.RESOLVED_UNIT)), false);
}
/**
* Create a {@link PolymerResolveHtmlTask} for the given source, marking the Polymer errors as
* being in-process.
*
* @param source the source whose content is to be resolved
* @param htmlEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createPolymerResolveHtmlTask(Source source, HtmlEntry htmlEntry) {
if (htmlEntry.getState(HtmlEntry.RESOLVED_UNIT) != CacheState.VALID) {
return createResolveHtmlTask(source, htmlEntry);
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setState(HtmlEntry.POLYMER_RESOLUTION_ERRORS, CacheState.IN_PROCESS);
cache.put(source, htmlCopy);
return new TaskData(new PolymerResolveHtmlTask(
this,
source,
htmlCopy.getModificationTime(),
htmlEntry.getValue(SourceEntry.LINE_INFO),
htmlCopy.getValue(HtmlEntry.RESOLVED_UNIT)), false);
}
/**
* Create a {@link ResolveAngularComponentTemplateTask} for the given source, marking the angular
* errors as being in-process.
*
* @param source the source whose content is to be resolved
* @param htmlEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createResolveAngularComponentTemplateTask(Source source, HtmlEntry htmlEntry) {
if (htmlEntry.getState(HtmlEntry.RESOLVED_UNIT) != CacheState.VALID) {
return createResolveHtmlTask(source, htmlEntry);
}
AngularApplication application = htmlEntry.getValue(HtmlEntry.ANGULAR_APPLICATION);
AngularComponentElement component = htmlEntry.getValue(HtmlEntry.ANGULAR_COMPONENT);
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setState(HtmlEntry.ANGULAR_ERRORS, CacheState.IN_PROCESS);
cache.put(source, htmlCopy);
return new TaskData(new ResolveAngularComponentTemplateTask(
this,
source,
htmlCopy.getModificationTime(),
htmlCopy.getValue(HtmlEntry.RESOLVED_UNIT),
component,
application), false);
}
/**
* Create a {@link ResolveAngularEntryHtmlTask} for the given source, marking the angular entry as
* being in-process.
*
* @param source the source whose content is to be resolved
* @param htmlEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createResolveAngularEntryHtmlTask(Source source, HtmlEntry htmlEntry) {
if (htmlEntry.getState(HtmlEntry.RESOLVED_UNIT) != CacheState.VALID) {
return createResolveHtmlTask(source, htmlEntry);
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setState(HtmlEntry.ANGULAR_ENTRY, CacheState.IN_PROCESS);
cache.put(source, htmlCopy);
return new TaskData(new ResolveAngularEntryHtmlTask(
this,
source,
htmlCopy.getModificationTime(),
htmlCopy.getValue(HtmlEntry.RESOLVED_UNIT)), false);
}
/**
* Create a {@link ResolveDartLibraryTask} for the given source, marking ? as being in-process.
*
* @param source the source whose content is to be resolved
* @param dartEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createResolveDartLibraryTask(Source source, DartEntry dartEntry) {
try {
CycleBuilder builder = new CycleBuilder();
builder.computeCycleContaining(source);
TaskData taskData = builder.getTaskData();
if (taskData != null) {
return taskData;
}
return new TaskData(new ResolveDartLibraryCycleTask(
this,
source,
source,
builder.getLibrariesInCycle()), false);
} catch (AnalysisException exception) {
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.recordResolutionError(exception);
cache.put(source, dartCopy);
AnalysisEngine.getInstance().getLogger().logError(
"Internal error trying to create a ResolveDartLibraryTask",
exception);
}
return new TaskData(null, false);
}
/**
* Create a {@link ResolveHtmlTask} for the given source, marking the resolved unit as being
* in-process.
*
* @param source the source whose content is to be resolved
* @param htmlEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createResolveHtmlTask(Source source, HtmlEntry htmlEntry) {
if (htmlEntry.getState(HtmlEntry.PARSED_UNIT) != CacheState.VALID) {
return createParseHtmlTask(source, htmlEntry);
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setState(HtmlEntry.RESOLVED_UNIT, CacheState.IN_PROCESS);
cache.put(source, htmlCopy);
return new TaskData(new ResolveHtmlTask(
this,
source,
htmlCopy.getModificationTime(),
htmlCopy.getValue(HtmlEntry.PARSED_UNIT)), false);
}
/**
* Create a {@link ScanDartTask} for the given source, marking the scan errors as being
* in-process.
*
* @param source the source whose content is to be scanned
* @param dartEntry the entry for the source
* @return task data representing the created task
*/
private TaskData createScanDartTask(Source source, DartEntry dartEntry) {
if (dartEntry.getState(SourceEntry.CONTENT) != CacheState.VALID) {
return createGetContentTask(source, dartEntry);
}
CharSequence content = dartEntry.getValue(SourceEntry.CONTENT);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.setState(SourceEntry.CONTENT, CacheState.FLUSHED);
dartCopy.setState(DartEntry.SCAN_ERRORS, CacheState.IN_PROCESS);
cache.put(source, dartCopy);
return new TaskData(
new ScanDartTask(this, source, dartCopy.getModificationTime(), content),
false);
}
/**
* Create a source information object suitable for the given source. Return the source information
* object that was created, or {@code null} if the source should not be tracked by this context.
*
* @param source the source for which an information object is being created
* @param explicitlyAdded {@code true} if the source was explicitly added to the context
* @return the source information object that was created
*/
private SourceEntry createSourceEntry(Source source, boolean explicitlyAdded) {
String name = source.getShortName();
if (AnalysisEngine.isHtmlFileName(name)) {
HtmlEntryImpl htmlEntry = new HtmlEntryImpl();
htmlEntry.setModificationTime(getModificationStamp(source));
htmlEntry.setExplicitlyAdded(explicitlyAdded);
cache.put(source, htmlEntry);
return htmlEntry;
} else {
DartEntryImpl dartEntry = new DartEntryImpl();
dartEntry.setModificationTime(getModificationStamp(source));
dartEntry.setExplicitlyAdded(explicitlyAdded);
cache.put(source, dartEntry);
return dartEntry;
}
}
/**
* Return a string with debugging information about the given source (the full name and
* modification stamp of the source).
*
* @param source the source for which a debugging string is to be produced
* @return debugging information about the given source
*/
private String debuggingString(Source source) {
return "'" + source.getFullName() + "' [" + getModificationStamp(source) + "]";
}
/**
* Return an array containing all of the change notices that are waiting to be returned. If there
* are no notices, then return either {@code null} or an empty array, depending on the value of
* the argument.
*
* @param nullIfEmpty {@code true} if {@code null} should be returned when there are no notices
* @return the change notices that are waiting to be returned
*/
private ChangeNotice[] getChangeNotices(boolean nullIfEmpty) {
synchronized (cacheLock) {
if (pendingNotices.isEmpty()) {
if (nullIfEmpty) {
return null;
}
return ChangeNoticeImpl.EMPTY_ARRAY;
}
ChangeNotice[] notices = pendingNotices.values().toArray(
new ChangeNotice[pendingNotices.size()]);
pendingNotices.clear();
return notices;
}
}
/**
* Given a source for a Dart file and the library that contains it, return the data represented by
* the given descriptor that is associated with that source. This method assumes that the data can
* be produced by generating hints for the library if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param dartEntry the entry representing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private <E> E getDartHintData(Source unitSource, Source librarySource, DartEntry dartEntry,
DataDescriptor<E> descriptor) throws AnalysisException {
dartEntry = cacheDartHintData(unitSource, librarySource, dartEntry, descriptor);
if (descriptor == DartEntry.ELEMENT) {
return dartEntry.getValue(descriptor);
}
return dartEntry.getValueInLibrary(descriptor, librarySource);
}
/**
* Given a source for a Dart file, return the data represented by the given descriptor that is
* associated with that source. This method assumes that the data can be produced by parsing the
* source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
@SuppressWarnings("unchecked")
private <E> E getDartParseData(Source source, DartEntry dartEntry, DataDescriptor<E> descriptor)
throws AnalysisException {
dartEntry = cacheDartParseData(source, dartEntry, descriptor);
if (descriptor == DartEntry.PARSED_UNIT) {
accessedAst(source);
return (E) dartEntry.getAnyParsedCompilationUnit();
}
return dartEntry.getValue(descriptor);
}
/**
* Given a source for a Dart file, return the data represented by the given descriptor that is
* associated with that source, or the given default value if the source is not a Dart file. This
* method assumes that the data can be produced by parsing the source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @param defaultValue the value to be returned if the source is not a Dart file
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
private <E> E getDartParseData(Source source, DataDescriptor<E> descriptor, E defaultValue)
throws AnalysisException {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
return defaultValue;
}
try {
return getDartParseData(source, dartEntry, descriptor);
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute " + descriptor.toString(),
exception);
return defaultValue;
}
}
/**
* Given a source for a Dart file and the library that contains it, return the data represented by
* the given descriptor that is associated with that source. This method assumes that the data can
* be produced by resolving the source in the context of the library if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param dartEntry the entry representing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private <E> E getDartResolutionData(Source unitSource, Source librarySource, DartEntry dartEntry,
DataDescriptor<E> descriptor) throws AnalysisException {
dartEntry = cacheDartResolutionData(unitSource, librarySource, dartEntry, descriptor);
if (descriptor == DartEntry.ELEMENT) {
return dartEntry.getValue(descriptor);
} else if (descriptor == DartEntry.RESOLVED_UNIT) {
accessedAst(unitSource);
}
return dartEntry.getValueInLibrary(descriptor, librarySource);
}
/**
* Given a source for a Dart file and the library that contains it, return the data represented by
* the given descriptor that is associated with that source, or the given default value if the
* source is not a Dart file. This method assumes that the data can be produced by resolving the
* source in the context of the library if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @param defaultValue the value to be returned if the source is not a Dart file
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private <E> E getDartResolutionData(Source unitSource, Source librarySource,
DataDescriptor<E> descriptor, E defaultValue) throws AnalysisException {
DartEntry dartEntry = getReadableDartEntry(unitSource);
if (dartEntry == null) {
return defaultValue;
}
try {
return getDartResolutionData(unitSource, librarySource, dartEntry, descriptor);
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute " + descriptor.toString(),
exception);
return defaultValue;
}
}
/**
* Given a source for a Dart file, return the data represented by the given descriptor that is
* associated with that source. This method assumes that the data can be produced by scanning the
* source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param dartEntry the cache entry associated with the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be scanned
*/
private <E> E getDartScanData(Source source, DartEntry dartEntry, DataDescriptor<E> descriptor)
throws AnalysisException {
dartEntry = cacheDartScanData(source, dartEntry, descriptor);
return dartEntry.getValue(descriptor);
}
/**
* Given a source for a Dart file, return the data represented by the given descriptor that is
* associated with that source, or the given default value if the source is not a Dart file. This
* method assumes that the data can be produced by scanning the source if it is not already
* cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @param defaultValue the value to be returned if the source is not a Dart file
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be scanned
*/
private <E> E getDartScanData(Source source, DataDescriptor<E> descriptor, E defaultValue)
throws AnalysisException {
DartEntry dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
return defaultValue;
}
try {
return getDartScanData(source, dartEntry, descriptor);
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute " + descriptor.toString(),
exception);
return defaultValue;
}
}
/**
* Given a source for a Dart file and the library that contains it, return the data represented by
* the given descriptor that is associated with that source. This method assumes that the data can
* be produced by verifying the source within the given library if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param unitSource the source representing the Dart file
* @param librarySource the source representing the library containing the Dart file
* @param dartEntry the entry representing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private <E> E getDartVerificationData(Source unitSource, Source librarySource,
DartEntry dartEntry, DataDescriptor<E> descriptor) throws AnalysisException {
dartEntry = cacheDartVerificationData(unitSource, librarySource, dartEntry, descriptor);
return dartEntry.getValueInLibrary(descriptor, librarySource);
}
/**
* Given a source for an HTML file, return the data represented by the given descriptor that is
* associated with that source, or the given default value if the source is not an HTML file. This
* method assumes that the data can be produced by parsing the source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the Dart file
* @param descriptor the descriptor representing the data to be returned
* @param defaultValue the value to be returned if the source is not an HTML file
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be parsed
*/
@SuppressWarnings("unchecked")
private <E> E getHtmlParseData(Source source, DataDescriptor<E> descriptor, E defaultValue)
throws AnalysisException {
HtmlEntry htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
return defaultValue;
}
htmlEntry = cacheHtmlParseData(source, htmlEntry, descriptor);
if (descriptor == HtmlEntry.PARSED_UNIT) {
accessedAst(source);
return (E) htmlEntry.getAnyParsedUnit();
}
return htmlEntry.getValue(descriptor);
}
/**
* Given a source for an HTML file, return the data represented by the given descriptor that is
* associated with that source, or the given default value if the source is not an HTML file. This
* method assumes that the data can be produced by resolving the source if it is not already
* cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the HTML file
* @param descriptor the descriptor representing the data to be returned
* @param defaultValue the value to be returned if the source is not an HTML file
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private <E> E getHtmlResolutionData(Source source, DataDescriptor<E> descriptor, E defaultValue)
throws AnalysisException {
HtmlEntry htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
return defaultValue;
}
try {
return getHtmlResolutionData(source, htmlEntry, descriptor);
} catch (ObsoleteSourceAnalysisException exception) {
AnalysisEngine.getInstance().getLogger().logInformation(
"Could not compute " + descriptor.toString(),
exception);
return defaultValue;
}
}
/**
* Given a source for an HTML file, return the data represented by the given descriptor that is
* associated with that source. This method assumes that the data can be produced by resolving the
* source if it is not already cached.
* <p>
* <b>Note:</b> This method cannot be used in an async environment.
*
* @param source the source representing the HTML file
* @param htmlEntry the entry representing the HTML file
* @param descriptor the descriptor representing the data to be returned
* @return the requested data about the given source
* @throws AnalysisException if data could not be returned because the source could not be
* resolved
*/
private <E> E getHtmlResolutionData(Source source, HtmlEntry htmlEntry,
DataDescriptor<E> descriptor) throws AnalysisException {
htmlEntry = cacheHtmlResolutionData(source, htmlEntry, descriptor);
if (descriptor == HtmlEntry.RESOLVED_UNIT) {
accessedAst(source);
}
return htmlEntry.getValue(descriptor);
}
/**
* Look through the cache for a task that needs to be performed. Return the task that was found,
* or {@code null} if there is no more work to be done.
*
* @return the next task that needs to be performed
*/
private AnalysisTask getNextAnalysisTask() {
synchronized (cacheLock) {
boolean hintsEnabled = options.getHint();
boolean hasBlockedTask = false;
//
// Look for incremental analysis
//
if (incrementalAnalysisCache != null && incrementalAnalysisCache.hasWork()) {
AnalysisTask task = new IncrementalAnalysisTask(this, incrementalAnalysisCache);
incrementalAnalysisCache = null;
return task;
}
//
// Look for a priority source that needs to be analyzed.
//
int priorityCount = priorityOrder.length;
for (int i = 0; i < priorityCount; i++) {
Source source = priorityOrder[i];
TaskData taskData = getNextAnalysisTaskForSource(
source,
cache.get(source),
true,
hintsEnabled);
AnalysisTask task = taskData.getTask();
if (task != null) {
return task;
} else if (taskData.isBlocked()) {
hasBlockedTask = true;
}
}
if (neededForResolution != null) {
ArrayList<Source> sourcesToRemove = new ArrayList<Source>();
for (Source source : neededForResolution) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
if (!dartEntry.hasResolvableCompilationUnit()) {
if (dartEntry.getState(DartEntry.PARSED_UNIT) == CacheState.ERROR) {
sourcesToRemove.add(source);
} else {
TaskData taskData = createParseDartTask(source, dartEntry);
AnalysisTask task = taskData.getTask();
if (task != null) {
return task;
} else if (taskData.isBlocked()) {
hasBlockedTask = true;
}
}
}
}
}
int count = sourcesToRemove.size();
for (int i = 0; i < count; i++) {
neededForResolution.remove(sourcesToRemove.get(i));
}
}
//
// Look for a non-priority source that needs to be analyzed.
//
ArrayList<Source> sourcesToRemove = new ArrayList<Source>();
WorkManager.WorkIterator sources = workManager.iterator();
try {
while (sources.hasNext()) {
Source source = sources.next();
TaskData taskData = getNextAnalysisTaskForSource(
source,
cache.get(source),
false,
hintsEnabled);
AnalysisTask task = taskData.getTask();
if (task != null) {
return task;
} else if (taskData.isBlocked()) {
hasBlockedTask = true;
} else {
sourcesToRemove.add(source);
}
}
} finally {
int count = sourcesToRemove.size();
for (int i = 0; i < count; i++) {
workManager.remove(sourcesToRemove.get(i));
}
}
if (hasBlockedTask) {
// All of the analysis work is blocked waiting for an asynchronous task to complete.
return WaitForAsyncTask.getInstance();
}
return null;
}
}
/**
* Look at the given source to see whether a task needs to be performed related to it. Return the
* task that should be performed, or {@code null} if there is no more work to be done for the
* source.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param source the source to be checked
* @param sourceEntry the cache entry associated with the source
* @param isPriority {@code true} if the source is a priority source
* @param hintsEnabled {@code true} if hints are currently enabled
* @return the next task that needs to be performed for the given source
*/
private TaskData getNextAnalysisTaskForSource(Source source, SourceEntry sourceEntry,
boolean isPriority, boolean hintsEnabled) {
// Refuse to generate tasks for html based files that are above 1500 KB
if (isTooBigHtmlSourceEntry(source, sourceEntry)) {
// TODO (jwren) we still need to report an error of some kind back to the client.
return new TaskData(null, false);
}
if (sourceEntry == null) {
return new TaskData(null, false);
}
CacheState contentState = sourceEntry.getState(SourceEntry.CONTENT);
if (contentState == CacheState.INVALID) {
return createGetContentTask(source, sourceEntry);
} else if (contentState == CacheState.IN_PROCESS) {
// We are already in the process of getting the content. There's nothing else we can do with
// this source until that's complete.
return new TaskData(null, true);
} else if (contentState == CacheState.ERROR) {
// We have done all of the analysis we can for this source because we cannot get its content.
return new TaskData(null, false);
}
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
CacheState scanErrorsState = dartEntry.getState(DartEntry.SCAN_ERRORS);
if (scanErrorsState == CacheState.INVALID
|| (isPriority && scanErrorsState == CacheState.FLUSHED)) {
return createScanDartTask(source, dartEntry);
}
CacheState parseErrorsState = dartEntry.getState(DartEntry.PARSE_ERRORS);
if (parseErrorsState == CacheState.INVALID
|| (isPriority && parseErrorsState == CacheState.FLUSHED)) {
return createParseDartTask(source, dartEntry);
}
if (isPriority && parseErrorsState != CacheState.ERROR) {
if (!dartEntry.hasResolvableCompilationUnit()) {
return createParseDartTask(source, dartEntry);
}
}
SourceKind kind = dartEntry.getValue(DartEntry.SOURCE_KIND);
if (kind == SourceKind.UNKNOWN) {
return createParseDartTask(source, dartEntry);
} else if (kind == SourceKind.LIBRARY) {
CacheState elementState = dartEntry.getState(DartEntry.ELEMENT);
if (elementState == CacheState.INVALID) {
return createResolveDartLibraryTask(source, dartEntry);
}
}
Source[] librariesContaining = dartEntry.getValue(DartEntry.CONTAINING_LIBRARIES);
for (Source librarySource : librariesContaining) {
SourceEntry librarySourceEntry = cache.get(librarySource);
if (librarySourceEntry instanceof DartEntry) {
DartEntry libraryEntry = (DartEntry) librarySourceEntry;
CacheState elementState = libraryEntry.getState(DartEntry.ELEMENT);
if (elementState == CacheState.INVALID
|| (isPriority && elementState == CacheState.FLUSHED)) {
//return createResolveDartLibraryTask(librarySource, (DartEntry) libraryEntry);
DartEntryImpl libraryCopy = libraryEntry.getWritableCopy();
libraryCopy.setState(DartEntry.ELEMENT, CacheState.IN_PROCESS);
cache.put(librarySource, libraryCopy);
return new TaskData(new ResolveDartLibraryTask(this, source, librarySource), false);
}
CacheState resolvedUnitState = dartEntry.getStateInLibrary(
DartEntry.RESOLVED_UNIT,
librarySource);
if (resolvedUnitState == CacheState.INVALID
|| (isPriority && resolvedUnitState == CacheState.FLUSHED)) {
//
// The commented out lines below are an optimization that doesn't quite work yet. The
// problem is that if the source was not resolved because it wasn't part of any library,
// then there won't be any elements in the element model that we can use to resolve it.
//
//LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
//if (libraryElement != null) {
// return new ResolveDartUnitTask(this, source, libraryElement);
//}
// Possibly replace with: return createResolveDartLibraryTask(librarySource, (DartEntry) libraryEntry);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.setStateInLibrary(
DartEntry.RESOLVED_UNIT,
librarySource,
CacheState.IN_PROCESS);
cache.put(source, dartCopy);
return new TaskData(new ResolveDartLibraryTask(this, source, librarySource), false);
}
if (generateSdkErrors || !source.isInSystemLibrary()) {
CacheState verificationErrorsState = dartEntry.getStateInLibrary(
DartEntry.VERIFICATION_ERRORS,
librarySource);
if (verificationErrorsState == CacheState.INVALID
|| (isPriority && verificationErrorsState == CacheState.FLUSHED)) {
return createGenerateDartErrorsTask(source, dartEntry, librarySource, libraryEntry);
}
if (hintsEnabled) {
CacheState hintsState = dartEntry.getStateInLibrary(DartEntry.HINTS, librarySource);
if (hintsState == CacheState.INVALID
|| (isPriority && hintsState == CacheState.FLUSHED)) {
return createGenerateDartHintsTask(source, dartEntry, librarySource, libraryEntry);
}
}
}
}
}
} else if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
CacheState parseErrorsState = htmlEntry.getState(HtmlEntry.PARSE_ERRORS);
if (parseErrorsState == CacheState.INVALID
|| (isPriority && parseErrorsState == CacheState.FLUSHED)) {
return createParseHtmlTask(source, htmlEntry);
}
if (isPriority && parseErrorsState != CacheState.ERROR) {
HtmlUnit parsedUnit = htmlEntry.getAnyParsedUnit();
if (parsedUnit == null) {
return createParseHtmlTask(source, htmlEntry);
}
}
CacheState resolvedUnitState = htmlEntry.getState(HtmlEntry.RESOLVED_UNIT);
if (resolvedUnitState == CacheState.INVALID
|| (isPriority && resolvedUnitState == CacheState.FLUSHED)) {
return createResolveHtmlTask(source, htmlEntry);
}
//
// Angular support
//
if (options.getAnalyzeAngular()) {
// Try to resolve the HTML as an Angular entry point.
CacheState angularEntryState = htmlEntry.getState(HtmlEntry.ANGULAR_ENTRY);
if (angularEntryState == CacheState.INVALID
|| (isPriority && angularEntryState == CacheState.FLUSHED)) {
return createResolveAngularEntryHtmlTask(source, htmlEntry);
}
// Try to resolve the HTML as an Angular application part.
CacheState angularErrorsState = htmlEntry.getState(HtmlEntry.ANGULAR_ERRORS);
if (angularErrorsState == CacheState.INVALID
|| (isPriority && angularErrorsState == CacheState.FLUSHED)) {
return createResolveAngularComponentTemplateTask(source, htmlEntry);
}
}
//
// Polymer support
//
if (options.getAnalyzePolymer()) {
// Build elements.
CacheState polymerBuildErrorsState = htmlEntry.getState(HtmlEntry.POLYMER_BUILD_ERRORS);
if (polymerBuildErrorsState == CacheState.INVALID
|| (isPriority && polymerBuildErrorsState == CacheState.FLUSHED)) {
return createPolymerBuildHtmlTask(source, htmlEntry);
}
// Resolve references.
CacheState polymerResolutionErrorsState = htmlEntry.getState(HtmlEntry.POLYMER_RESOLUTION_ERRORS);
if (polymerResolutionErrorsState == CacheState.INVALID
|| (isPriority && polymerResolutionErrorsState == CacheState.FLUSHED)) {
return createPolymerResolveHtmlTask(source, htmlEntry);
}
}
}
return new TaskData(null, false);
}
/**
* Return a change notice for the given source, creating one if one does not already exist.
*
* @param source the source for which changes are being reported
* @return a change notice for the given source
*/
private ChangeNoticeImpl getNotice(Source source) {
ChangeNoticeImpl notice = pendingNotices.get(source);
if (notice == null) {
notice = new ChangeNoticeImpl(source);
pendingNotices.put(source, notice);
}
return notice;
}
/**
* Return the cache entry associated with the given source, or {@code null} if the source is not a
* Dart file.
*
* @param source the source for which a cache entry is being sought
* @return the source cache entry associated with the given source
*/
private DartEntry getReadableDartEntry(Source source) {
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
sourceEntry = createSourceEntry(source, false);
}
if (sourceEntry instanceof DartEntry) {
return (DartEntry) sourceEntry;
}
return null;
}
}
/**
* Return the cache entry associated with the given source, or {@code null} if the source is not
* an HTML file.
*
* @param source the source for which a cache entry is being sought
* @return the source cache entry associated with the given source
*/
private HtmlEntry getReadableHtmlEntry(Source source) {
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
sourceEntry = createSourceEntry(source, false);
}
if (sourceEntry instanceof HtmlEntry) {
return (HtmlEntry) sourceEntry;
}
return null;
}
}
/**
* Return the cache entry associated with the given source, creating it if necessary.
*
* @param source the source for which a cache entry is being sought
* @return the source cache entry associated with the given source
*/
private SourceEntry getReadableSourceEntry(Source source) {
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
sourceEntry = createSourceEntry(source, false);
}
return sourceEntry;
}
}
/**
* Return the cache entry associated with the given source, or {@code null} if there is no entry
* associated with the source.
*
* @param source the source for which a cache entry is being sought
* @return the source cache entry associated with the given source
*/
private SourceEntry getReadableSourceEntryOrNull(Source source) {
synchronized (cacheLock) {
return cache.get(source);
}
}
/**
* Return a resolved compilation unit corresponding to the given element in the given library, or
* {@code null} if the information is not cached.
*
* @param element the element representing the compilation unit
* @param librarySource the source representing the library containing the unit
* @return the specified resolved compilation unit
*/
private TimestampedData<CompilationUnit> getResolvedUnit(CompilationUnitElement element,
Source librarySource) {
SourceEntry sourceEntry = cache.get(element.getSource());
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
if (dartEntry.getStateInLibrary(DartEntry.RESOLVED_UNIT, librarySource) == CacheState.VALID) {
return new TimestampedData<CompilationUnit>(
dartEntry.getModificationTime(),
dartEntry.getValueInLibrary(DartEntry.RESOLVED_UNIT, librarySource));
}
}
return null;
}
/**
* Return an array containing all of the sources known to this context that have the given kind.
*
* @param kind the kind of sources to be returned
* @return all of the sources known to this context that have the given kind
*/
private Source[] getSources(SourceKind kind) {
ArrayList<Source> sources = new ArrayList<Source>();
synchronized (cacheLock) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
if (iterator.getValue().getKind() == kind) {
sources.add(iterator.getKey());
}
}
}
return sources.toArray(new Source[sources.size()]);
}
/**
* Look at the given source to see whether a task needs to be performed related to it. If so, add
* the source to the set of sources that need to be processed. This method duplicates, and must
* therefore be kept in sync with,
* {@link #getNextAnalysisTask(Source, SourceEntry, boolean, boolean)}. This method is intended to
* be used for testing purposes only.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param source the source to be checked
* @param sourceEntry the cache entry associated with the source
* @param isPriority {@code true} if the source is a priority source
* @param hintsEnabled {@code true} if hints are currently enabled
* @param sources the set to which sources should be added
*/
private void getSourcesNeedingProcessing(Source source, SourceEntry sourceEntry,
boolean isPriority, boolean hintsEnabled, HashSet<Source> sources) {
if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
CacheState scanErrorsState = dartEntry.getState(DartEntry.SCAN_ERRORS);
if (scanErrorsState == CacheState.INVALID
|| (isPriority && scanErrorsState == CacheState.FLUSHED)) {
sources.add(source);
return;
}
CacheState parseErrorsState = dartEntry.getState(DartEntry.PARSE_ERRORS);
if (parseErrorsState == CacheState.INVALID
|| (isPriority && parseErrorsState == CacheState.FLUSHED)) {
sources.add(source);
return;
}
if (isPriority) {
if (!dartEntry.hasResolvableCompilationUnit()) {
sources.add(source);
return;
}
}
for (Source librarySource : getLibrariesContaining(source)) {
SourceEntry libraryEntry = cache.get(librarySource);
if (libraryEntry instanceof DartEntry) {
CacheState elementState = libraryEntry.getState(DartEntry.ELEMENT);
if (elementState == CacheState.INVALID
|| (isPriority && elementState == CacheState.FLUSHED)) {
sources.add(source);
return;
}
CacheState resolvedUnitState = dartEntry.getStateInLibrary(
DartEntry.RESOLVED_UNIT,
librarySource);
if (resolvedUnitState == CacheState.INVALID
|| (isPriority && resolvedUnitState == CacheState.FLUSHED)) {
LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
if (libraryElement != null) {
sources.add(source);
return;
}
}
if (generateSdkErrors || !source.isInSystemLibrary()) {
CacheState verificationErrorsState = dartEntry.getStateInLibrary(
DartEntry.VERIFICATION_ERRORS,
librarySource);
if (verificationErrorsState == CacheState.INVALID
|| (isPriority && verificationErrorsState == CacheState.FLUSHED)) {
LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
if (libraryElement != null) {
sources.add(source);
return;
}
}
if (hintsEnabled) {
CacheState hintsState = dartEntry.getStateInLibrary(DartEntry.HINTS, librarySource);
if (hintsState == CacheState.INVALID
|| (isPriority && hintsState == CacheState.FLUSHED)) {
LibraryElement libraryElement = libraryEntry.getValue(DartEntry.ELEMENT);
if (libraryElement != null) {
sources.add(source);
return;
}
}
}
}
}
}
} else if (sourceEntry instanceof HtmlEntry) {
HtmlEntry htmlEntry = (HtmlEntry) sourceEntry;
CacheState parsedUnitState = htmlEntry.getState(HtmlEntry.PARSED_UNIT);
if (parsedUnitState == CacheState.INVALID
|| (isPriority && parsedUnitState == CacheState.FLUSHED)) {
sources.add(source);
return;
}
CacheState resolvedUnitState = htmlEntry.getState(HtmlEntry.RESOLVED_UNIT);
if (resolvedUnitState == CacheState.INVALID
|| (isPriority && resolvedUnitState == CacheState.FLUSHED)) {
sources.add(source);
return;
}
// Angular
if (options.getAnalyzeAngular()) {
CacheState angularErrorsState = htmlEntry.getState(HtmlEntry.ANGULAR_ERRORS);
if (angularErrorsState == CacheState.INVALID
|| (isPriority && angularErrorsState == CacheState.FLUSHED)) {
AngularApplication entryInfo = htmlEntry.getValue(HtmlEntry.ANGULAR_ENTRY);
if (entryInfo != null) {
sources.add(source);
return;
}
AngularApplication applicationInfo = htmlEntry.getValue(HtmlEntry.ANGULAR_APPLICATION);
if (applicationInfo != null) {
AngularComponentElement component = htmlEntry.getValue(HtmlEntry.ANGULAR_COMPONENT);
if (component != null) {
sources.add(source);
return;
}
}
}
}
// Polymer
if (options.getAnalyzePolymer()) {
// Elements building.
CacheState polymerBuildErrorsState = htmlEntry.getState(HtmlEntry.POLYMER_BUILD_ERRORS);
if (polymerBuildErrorsState == CacheState.INVALID
|| (isPriority && polymerBuildErrorsState == CacheState.FLUSHED)) {
sources.add(source);
}
// Resolution.
CacheState polymerResolutionErrorsState = htmlEntry.getState(HtmlEntry.POLYMER_RESOLUTION_ERRORS);
if (polymerResolutionErrorsState == CacheState.INVALID
|| (isPriority && polymerResolutionErrorsState == CacheState.FLUSHED)) {
sources.add(source);
}
}
}
}
/**
* Invalidate all of the resolution results computed by this context.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param invalidateUris {@code true} if the cached results of converting URIs to source files
* should also be invalidated.
*/
private void invalidateAllLocalResolutionInformation(boolean invalidateUris) {
HashMap<Source, Source[]> oldPartMap = new HashMap<Source, Source[]>();
MapIterator<Source, SourceEntry> iterator = privatePartition.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
SourceEntry sourceEntry = iterator.getValue();
if (sourceEntry instanceof HtmlEntry) {
HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy();
htmlCopy.invalidateAllResolutionInformation(invalidateUris);
iterator.setValue(htmlCopy);
workManager.add(source, SourcePriority.HTML);
} else if (sourceEntry instanceof DartEntry) {
DartEntry dartEntry = (DartEntry) sourceEntry;
oldPartMap.put(source, dartEntry.getValue(DartEntry.INCLUDED_PARTS));
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.invalidateAllResolutionInformation(invalidateUris);
iterator.setValue(dartCopy);
workManager.add(source, computePriority(dartCopy));
}
}
removeFromPartsUsingMap(oldPartMap);
}
/**
* In response to a change to Angular entry point {@link HtmlElement}, invalidate any results that
* depend on it.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
* <p>
* <b>Note:</b> Any cache entries that were accessed before this method was invoked must be
* re-accessed after this method returns.
*
* @param entryCopy the {@link HtmlEntryImpl} of the (maybe) Angular entry point being invalidated
*/
private void invalidateAngularResolution(HtmlEntryImpl entryCopy) {
AngularApplication application = entryCopy.getValue(HtmlEntry.ANGULAR_ENTRY);
if (application == null) {
return;
}
angularApplications.remove(application);
// invalidate Entry
entryCopy.setState(HtmlEntry.ANGULAR_ENTRY, CacheState.INVALID);
// reset HTML sources
AngularElement[] oldAngularElements = application.getElements();
for (AngularElement angularElement : oldAngularElements) {
if (angularElement instanceof AngularHasTemplateElement) {
AngularHasTemplateElement hasTemplate = (AngularHasTemplateElement) angularElement;
Source templateSource = hasTemplate.getTemplateSource();
if (templateSource != null) {
HtmlEntry htmlEntry = getReadableHtmlEntry(templateSource);
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setValue(HtmlEntry.ANGULAR_APPLICATION, null);
htmlCopy.setValue(HtmlEntry.ANGULAR_COMPONENT, null);
htmlCopy.setState(HtmlEntry.ANGULAR_ERRORS, CacheState.INVALID);
cache.put(templateSource, htmlCopy);
workManager.add(templateSource, SourcePriority.HTML);
}
}
}
// reset Dart sources
Source[] oldElementSources = application.getElementSources();
for (Source elementSource : oldElementSources) {
DartEntry dartEntry = getReadableDartEntry(elementSource);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.setValue(DartEntry.ANGULAR_ERRORS, AnalysisError.NO_ERRORS);
cache.put(elementSource, dartCopy);
// notify about (disappeared) Angular errors
ChangeNoticeImpl notice = getNotice(elementSource);
notice.setErrors(dartCopy.getAllErrors(), dartEntry.getValue(SourceEntry.LINE_INFO));
}
}
/**
* In response to a change to at least one of the compilation units in the given library,
* invalidate any results that are dependent on the result of resolving that library.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
* <p>
* <b>Note:</b> Any cache entries that were accessed before this method was invoked must be
* re-accessed after this method returns.
*
* @param librarySource the source of the library being invalidated
*/
private void invalidateLibraryResolution(Source librarySource) {
// TODO(brianwilkerson) This could be optimized. There's no need to flush all of these entries
// if the public namespace hasn't changed, which will be a fairly common case. The question is
// whether we can afford the time to compute the namespace to look for differences.
DartEntry libraryEntry = getReadableDartEntry(librarySource);
if (libraryEntry != null) {
Source[] includedParts = libraryEntry.getValue(DartEntry.INCLUDED_PARTS);
DartEntryImpl libraryCopy = libraryEntry.getWritableCopy();
libraryCopy.invalidateAllResolutionInformation(false);
cache.put(librarySource, libraryCopy);
workManager.add(librarySource, SourcePriority.LIBRARY);
for (Source partSource : includedParts) {
SourceEntry partEntry = cache.get(partSource);
if (partEntry instanceof DartEntry) {
DartEntryImpl partCopy = ((DartEntry) partEntry).getWritableCopy();
partCopy.invalidateAllResolutionInformation(false);
cache.put(partSource, partCopy);
}
}
}
// invalidate Angular applications
List<AngularApplication> angularApplicationsCopy = Lists.newArrayList(angularApplications);
for (AngularApplication application : angularApplicationsCopy) {
if (application.dependsOn(librarySource)) {
Source entryPointSource = application.getEntryPoint();
HtmlEntry entry = getReadableHtmlEntry(entryPointSource);
HtmlEntryImpl entryCopy = entry.getWritableCopy();
invalidateAngularResolution(entryCopy);
cache.put(entryPointSource, entryCopy);
workManager.add(entryPointSource, SourcePriority.HTML);
}
}
}
/**
* Return {@code true} if this library is, or depends on, dart:html.
*
* @param library the library being tested
* @param visitedLibraries a collection of the libraries that have been visited, used to prevent
* infinite recursion
* @return {@code true} if this library is, or depends on, dart:html
*/
private boolean isClient(LibraryElement library, Source htmlSource,
HashSet<LibraryElement> visitedLibraries) {
if (visitedLibraries.contains(library)) {
return false;
}
if (library.getSource().equals(htmlSource)) {
return true;
}
visitedLibraries.add(library);
for (LibraryElement imported : library.getImportedLibraries()) {
if (isClient(imported, htmlSource, visitedLibraries)) {
return true;
}
}
for (LibraryElement exported : library.getExportedLibraries()) {
if (isClient(exported, htmlSource, visitedLibraries)) {
return true;
}
}
return false;
}
@DartExpressionBody("false")
private boolean isTooBigHtmlSourceEntry(Source source, SourceEntry sourceEntry) {
if (sourceEntry instanceof HtmlEntryImpl && source instanceof FileBasedSource) {
File file = ((FileBasedSource) source).getFile();
return file.length() > 1500 * 1024;
}
return false;
}
/**
* Log the given debugging information.
*
* @param message the message to be added to the log
*/
private void logInformation(String message) {
AnalysisEngine.getInstance().getLogger().logInformation(message);
}
/**
* Log the given debugging information.
*
* @param message the message to be added to the log
* @param exception the exception to be included in the log entry
*/
private void logInformation(String message, Throwable exception) {
if (exception == null) {
AnalysisEngine.getInstance().getLogger().logInformation(message);
} else {
AnalysisEngine.getInstance().getLogger().logInformation(message, exception);
}
}
/**
* Notify all of the analysis listeners that a task is about to be performed.
*
* @param taskDescription a human readable description of the task that is about to be performed
*/
private void notifyAboutToPerformTask(String taskDescription) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).aboutToPerformTask(this, taskDescription);
}
}
/**
* Notify all of the analysis listeners that the errors associated with the given source has been
* updated to the given errors.
*
* @param source the source containing the errors that were computed
* @param errors the errors that were computed
* @param lineInfo the line information associated with the source
*/
private void notifyErrors(Source source, AnalysisError[] errors, LineInfo lineInfo) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).computedErrors(this, source, errors, lineInfo);
}
}
/**
* Notify all of the analysis listeners that the given source is no longer included in the set of
* sources that are being analyzed.
*
* @param source the source that is no longer being analyzed
*/
private void notifyExcludedSource(Source source) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).excludedSource(this, source);
}
}
/**
* Notify all of the analysis listeners that the given source is now included in the set of
* sources that are being analyzed.
*
* @param source the source that is now being analyzed
*/
private void notifyIncludedSource(Source source) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).includedSource(this, source);
}
}
/**
* Notify all of the analysis listeners that the given Dart source was parsed.
*
* @param source the source that was parsed
* @param unit the result of parsing the source
*/
private void notifyParsedDart(Source source, CompilationUnit unit) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).parsedDart(this, source, unit);
}
}
/**
* Notify all of the analysis listeners that the given HTML source was parsed.
*
* @param source the source that was parsed
* @param unit the result of parsing the source
*/
private void notifyParsedHtml(Source source, HtmlUnit unit) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).parsedHtml(this, source, unit);
}
}
/**
* Notify all of the analysis listeners that the given Dart source was resolved.
*
* @param source the source that was resolved
* @param unit the result of resolving the source
*/
private void notifyResolvedDart(Source source, CompilationUnit unit) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).resolvedDart(this, source, unit);
}
}
/**
* Notify all of the analysis listeners that the given HTML source was resolved.
*
* @param source the source that was resolved
* @param unit the result of resolving the source
*/
private void notifyResolvedHtml(Source source, HtmlUnit unit) {
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.get(i).resolvedHtml(this, source, unit);
}
}
/**
* Updates {@link HtmlEntry}s that correspond to the previously known and new Angular application
* information.
*/
private void recordAngularEntryPoint(HtmlEntryImpl entry, ResolveAngularEntryHtmlTask task)
throws AnalysisException {
AngularApplication application = task.getApplication();
if (application != null) {
angularApplications.add(application);
// if this is an entry point, then we already resolved it
entry.setValue(HtmlEntry.ANGULAR_ERRORS, task.getEntryErrors());
// schedule HTML templates analysis
AngularElement[] newAngularElements = application.getElements();
for (AngularElement angularElement : newAngularElements) {
if (angularElement instanceof AngularHasTemplateElement) {
AngularHasTemplateElement hasTemplate = (AngularHasTemplateElement) angularElement;
Source templateSource = hasTemplate.getTemplateSource();
if (templateSource != null) {
HtmlEntry htmlEntry = getReadableHtmlEntry(templateSource);
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
htmlCopy.setValue(HtmlEntry.ANGULAR_APPLICATION, application);
if (hasTemplate instanceof AngularComponentElement) {
AngularComponentElement component = (AngularComponentElement) hasTemplate;
htmlCopy.setValue(HtmlEntry.ANGULAR_COMPONENT, component);
}
htmlCopy.setState(HtmlEntry.ANGULAR_ERRORS, CacheState.INVALID);
cache.put(templateSource, htmlCopy);
workManager.add(templateSource, SourcePriority.HTML);
}
}
}
// update Dart sources errors
Source[] newElementSources = application.getElementSources();
for (Source elementSource : newElementSources) {
DartEntry dartEntry = getReadableDartEntry(elementSource);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
dartCopy.setValue(DartEntry.ANGULAR_ERRORS, task.getErrors(elementSource));
cache.put(elementSource, dartCopy);
// notify about Dart errors
ChangeNoticeImpl notice = getNotice(elementSource);
notice.setErrors(dartCopy.getAllErrors(), computeLineInfo(elementSource));
}
}
// remember Angular entry point
entry.setValue(HtmlEntry.ANGULAR_ENTRY, application);
}
/**
* Given a cache entry and a library element, record the library element and other information
* gleaned from the element in the cache entry.
*
* @param dartCopy the cache entry in which data is to be recorded
* @param library the library element used to record information
* @param librarySource the source for the library used to record information
* @param htmlSource the source for the HTML library
*/
private void recordElementData(DartEntryImpl dartCopy, LibraryElement library,
Source librarySource, Source htmlSource) {
dartCopy.setValue(DartEntry.ELEMENT, library);
dartCopy.setValue(DartEntry.IS_LAUNCHABLE, library.getEntryPoint() != null);
dartCopy.setValue(
DartEntry.IS_CLIENT,
isClient(library, htmlSource, new HashSet<LibraryElement>()));
}
/**
* Record the results produced by performing a {@link GenerateDartErrorsTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private DartEntry recordGenerateDartErrorsTask(GenerateDartErrorsTask task)
throws AnalysisException {
Source source = task.getSource();
Source librarySource = task.getLibraryElement().getSource();
AnalysisException thrownException = task.getException();
DartEntry dartEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof DartEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to verify non-Dart file as a Dart file: "
+ source.getFullName());
}
dartEntry = (DartEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (dartEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ source.getFullName());
}
}
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null) {
dartCopy.setValueInLibrary(DartEntry.VERIFICATION_ERRORS, librarySource, task.getErrors());
ChangeNoticeImpl notice = getNotice(source);
notice.setErrors(dartCopy.getAllErrors(), dartCopy.getValue(SourceEntry.LINE_INFO));
} else {
dartCopy.recordVerificationErrorInLibrary(librarySource, thrownException);
}
cache.put(source, dartCopy);
dartEntry = dartCopy;
} else {
logInformation("Generated errors discarded for " + debuggingString(source)
+ "; sourceTime = " + sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ dartEntry.getModificationTime(), thrownException);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the source
// will be re-verified using the up-to-date sources.
//
// dartCopy.setState(DartEntry.VERIFICATION_ERRORS, librarySource, CacheState.INVALID);
removeFromParts(source, dartEntry);
dartCopy.invalidateAllInformation();
dartCopy.setModificationTime(sourceTime);
cache.removedAst(source);
workManager.add(source, SourcePriority.UNKNOWN);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-verify the source until there's a good chance
// that we'll be able to do so without error.
//
dartCopy.recordVerificationErrorInLibrary(librarySource, thrownException);
}
cache.put(source, dartCopy);
dartEntry = dartCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return dartEntry;
}
/**
* Record the results produced by performing a {@link GenerateDartHintsTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private DartEntry recordGenerateDartHintsTask(GenerateDartHintsTask task)
throws AnalysisException {
Source librarySource = task.getLibraryElement().getSource();
AnalysisException thrownException = task.getException();
DartEntry libraryEntry = null;
HashMap<Source, TimestampedData<AnalysisError[]>> hintMap = task.getHintMap();
if (hintMap == null) {
synchronized (cacheLock) {
// We don't have any information about which sources to mark as invalid other than the library
// source.
SourceEntry sourceEntry = cache.get(librarySource);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(librarySource);
} else if (!(sourceEntry instanceof DartEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to generate hints for non-Dart file as a Dart file: "
+ librarySource.getFullName());
}
if (thrownException == null) {
thrownException = new AnalysisException(
"GenerateDartHintsTask returned a null hint map without throwing an exception: "
+ librarySource.getFullName());
}
DartEntryImpl dartCopy = ((DartEntry) sourceEntry).getWritableCopy();
dartCopy.recordHintErrorInLibrary(librarySource, thrownException);
cache.put(librarySource, dartCopy);
}
throw thrownException;
}
for (Map.Entry<Source, TimestampedData<AnalysisError[]>> entry : hintMap.entrySet()) {
Source unitSource = entry.getKey();
TimestampedData<AnalysisError[]> results = entry.getValue();
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(unitSource);
if (!(sourceEntry instanceof DartEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to parse non-Dart file as a Dart file: "
+ unitSource.getFullName());
}
DartEntry dartEntry = (DartEntry) sourceEntry;
if (unitSource.equals(librarySource)) {
libraryEntry = dartEntry;
}
long sourceTime = getModificationStamp(unitSource);
long resultTime = results.getModificationTime();
if (sourceTime == resultTime) {
if (dartEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(unitSource);
dartEntry = getReadableDartEntry(unitSource);
if (dartEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ unitSource.getFullName());
}
}
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null) {
dartCopy.setValueInLibrary(DartEntry.HINTS, librarySource, results.getData());
ChangeNoticeImpl notice = getNotice(unitSource);
notice.setErrors(dartCopy.getAllErrors(), dartCopy.getValue(SourceEntry.LINE_INFO));
} else {
dartCopy.recordHintErrorInLibrary(librarySource, thrownException);
}
cache.put(unitSource, dartCopy);
dartEntry = dartCopy;
} else {
logInformation("Generated hints discarded for " + debuggingString(unitSource)
+ "; sourceTime = " + sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ dartEntry.getModificationTime(), thrownException);
if (dartEntry.getStateInLibrary(DartEntry.HINTS, librarySource) == CacheState.IN_PROCESS) {
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// dartCopy.setState(DartEntry.HINTS, librarySource, CacheState.INVALID);
removeFromParts(unitSource, dartEntry);
dartCopy.invalidateAllInformation();
dartCopy.setModificationTime(sourceTime);
cache.removedAst(unitSource);
workManager.add(unitSource, SourcePriority.UNKNOWN);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
dartCopy.recordHintErrorInLibrary(librarySource, thrownException);
}
cache.put(unitSource, dartCopy);
dartEntry = dartCopy;
}
}
}
}
if (thrownException != null) {
throw thrownException;
}
return libraryEntry;
}
/**
* Record the results produced by performing a {@link GetContentTask}.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private SourceEntry recordGetContentsTask(GetContentTask task) throws AnalysisException {
if (!task.isComplete()) {
return null;
}
Source source = task.getSource();
AnalysisException thrownException = task.getException();
SourceEntry sourceEntry = null;
synchronized (cacheLock) {
sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
}
SourceEntryImpl sourceCopy = sourceEntry.getWritableCopy();
if (thrownException == null) {
sourceCopy.setModificationTime(task.getModificationTime());
sourceCopy.setValue(SourceEntry.CONTENT, task.getContent());
} else {
sourceCopy.recordContentError(thrownException);
workManager.remove(source);
}
cache.put(source, sourceCopy);
sourceEntry = sourceCopy;
}
if (thrownException != null) {
throw thrownException;
}
return sourceEntry;
}
/**
* Record the results produced by performing a {@link IncrementalAnalysisTask}.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private DartEntry recordIncrementalAnalysisTaskResults(IncrementalAnalysisTask task)
throws AnalysisException {
synchronized (cacheLock) {
CompilationUnit unit = task.getCompilationUnit();
if (unit != null) {
ChangeNoticeImpl notice = getNotice(task.getSource());
notice.setCompilationUnit(unit);
incrementalAnalysisCache = IncrementalAnalysisCache.cacheResult(task.getCache(), unit);
}
}
return null;
}
/**
* Record the results produced by performing a {@link ParseDartTask}. If the results were computed
* from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private DartEntry recordParseDartTaskResults(ParseDartTask task) throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
DartEntry dartEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof DartEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to parse non-Dart file as a Dart file: "
+ source.getFullName());
}
dartEntry = (DartEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (dartEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ source.getFullName());
}
}
removeFromParts(source, dartEntry);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null) {
if (task.hasNonPartOfDirective()) {
dartCopy.setValue(DartEntry.SOURCE_KIND, SourceKind.LIBRARY);
dartCopy.setContainingLibrary(source);
workManager.add(source, SourcePriority.LIBRARY);
} else if (task.hasPartOfDirective()) {
dartCopy.setValue(DartEntry.SOURCE_KIND, SourceKind.PART);
dartCopy.removeContainingLibrary(source);
workManager.add(source, SourcePriority.NORMAL_PART);
} else {
// The file contains no directives.
List<Source> containingLibraries = dartCopy.getContainingLibraries();
if (containingLibraries.size() > 1
|| (containingLibraries.size() == 1 && !containingLibraries.get(0).equals(source))) {
dartCopy.setValue(DartEntry.SOURCE_KIND, SourceKind.PART);
dartCopy.removeContainingLibrary(source);
workManager.add(source, SourcePriority.NORMAL_PART);
} else {
dartCopy.setValue(DartEntry.SOURCE_KIND, SourceKind.LIBRARY);
dartCopy.setContainingLibrary(source);
workManager.add(source, SourcePriority.LIBRARY);
}
}
Source[] newParts = task.getIncludedSources();
for (int i = 0; i < newParts.length; i++) {
Source partSource = newParts[i];
DartEntry partEntry = getReadableDartEntry(partSource);
if (partEntry != null && partEntry != dartEntry) {
DartEntryImpl partCopy = partEntry.getWritableCopy();
// TODO(brianwilkerson) Change the kind of the "part" if it was marked as a library
// and it has no directives.
partCopy.addContainingLibrary(source);
cache.put(partSource, partCopy);
}
}
dartCopy.setValue(DartEntry.PARSED_UNIT, task.getCompilationUnit());
dartCopy.setValue(DartEntry.PARSE_ERRORS, task.getErrors());
dartCopy.setValue(DartEntry.EXPORTED_LIBRARIES, task.getExportedSources());
dartCopy.setValue(DartEntry.IMPORTED_LIBRARIES, task.getImportedSources());
dartCopy.setValue(DartEntry.INCLUDED_PARTS, newParts);
cache.storedAst(source);
ChangeNoticeImpl notice = getNotice(source);
notice.setErrors(dartCopy.getAllErrors(), task.getLineInfo());
// Verify that the incrementally parsed and resolved unit in the incremental cache
// is structurally equivalent to the fully parsed unit
incrementalAnalysisCache = IncrementalAnalysisCache.verifyStructure(
incrementalAnalysisCache,
source,
task.getCompilationUnit());
} else {
removeFromParts(source, dartEntry);
dartCopy.recordParseError(thrownException);
cache.removedAst(source);
}
cache.put(source, dartCopy);
dartEntry = dartCopy;
} else {
logInformation(
"Parse results discarded for " + debuggingString(source) + "; sourceTime = "
+ sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ dartEntry.getModificationTime(),
thrownException);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// dartCopy.recordParseNotInProcess();
removeFromParts(source, dartEntry);
dartCopy.invalidateAllInformation();
dartCopy.setModificationTime(sourceTime);
cache.removedAst(source);
workManager.add(source, SourcePriority.UNKNOWN);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
dartCopy.recordParseError(thrownException);
}
cache.put(source, dartCopy);
dartEntry = dartCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return dartEntry;
}
/**
* Record the results produced by performing a {@link ParseHtmlTask}. If the results were computed
* from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private HtmlEntry recordParseHtmlTaskResults(ParseHtmlTask task) throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
HtmlEntry htmlEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof HtmlEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent an HTML file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to parse non-HTML file as a HTML file: "
+ source.getFullName());
}
htmlEntry = (HtmlEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (htmlEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
throw new AnalysisException("An HTML file became a non-HTML file: "
+ source.getFullName());
}
}
HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy();
if (thrownException == null) {
LineInfo lineInfo = task.getLineInfo();
HtmlUnit unit = task.getHtmlUnit();
htmlCopy.setValue(SourceEntry.LINE_INFO, lineInfo);
htmlCopy.setValue(HtmlEntry.PARSED_UNIT, unit);
htmlCopy.setValue(HtmlEntry.PARSE_ERRORS, task.getErrors());
htmlCopy.setValue(HtmlEntry.REFERENCED_LIBRARIES, task.getReferencedLibraries());
cache.storedAst(source);
ChangeNoticeImpl notice = getNotice(source);
notice.setErrors(htmlCopy.getAllErrors(), lineInfo);
} else {
htmlCopy.recordParseError(thrownException);
cache.removedAst(source);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
} else {
logInformation(
"Parse results discarded for " + debuggingString(source) + "; sourceTime = "
+ sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ htmlEntry.getModificationTime(),
thrownException);
HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// if (htmlCopy.getState(SourceEntry.LINE_INFO) == CacheState.IN_PROCESS) {
// htmlCopy.setState(SourceEntry.LINE_INFO, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.PARSED_UNIT) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.PARSED_UNIT, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.REFERENCED_LIBRARIES) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.REFERENCED_LIBRARIES, CacheState.INVALID);
// }
htmlCopy.invalidateAllInformation();
htmlCopy.setModificationTime(sourceTime);
cache.removedAst(source);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
htmlCopy.recordParseError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return htmlEntry;
}
/**
* Record the results produced by performing a {@link PolymerBuildHtmlTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @throws AnalysisException if the results could not be recorded
*/
private HtmlEntry recordPolymerBuildHtmlTaskResults(PolymerBuildHtmlTask task)
throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
HtmlEntry htmlEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof HtmlEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent an HTML file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-HTML file as an HTML file: "
+ source.getFullName());
}
htmlEntry = (HtmlEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (htmlEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
throw new AnalysisException("An HTML file became a non-HTML file: "
+ source.getFullName());
}
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null) {
htmlCopy.setValue(HtmlEntry.POLYMER_BUILD_ERRORS, task.getErrors());
// notify about errors
ChangeNoticeImpl notice = getNotice(source);
notice.setErrors(htmlCopy.getAllErrors(), htmlCopy.getValue(SourceEntry.LINE_INFO));
} else {
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
} else {
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
htmlCopy.invalidateAllInformation();
htmlCopy.setModificationTime(sourceTime);
cache.removedAst(source);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return htmlEntry;
}
/**
* Record the results produced by performing a {@link PolymerResolveHtmlTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @throws AnalysisException if the results could not be recorded
*/
private HtmlEntry recordPolymerResolveHtmlTaskResults(PolymerResolveHtmlTask task)
throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
HtmlEntry htmlEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof HtmlEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent an HTML file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-HTML file as an HTML file: "
+ source.getFullName());
}
htmlEntry = (HtmlEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (htmlEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
throw new AnalysisException("An HTML file became a non-HTML file: "
+ source.getFullName());
}
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null) {
htmlCopy.setValue(HtmlEntry.POLYMER_RESOLUTION_ERRORS, task.getErrors());
// notify about errors
ChangeNoticeImpl notice = getNotice(source);
notice.setErrors(htmlCopy.getAllErrors(), htmlCopy.getValue(SourceEntry.LINE_INFO));
} else {
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
} else {
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
htmlCopy.invalidateAllInformation();
htmlCopy.setModificationTime(sourceTime);
cache.removedAst(source);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return htmlEntry;
}
/**
* Record the results produced by performing a {@link ResolveAngularComponentTemplateTask}. If the
* results were computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @throws AnalysisException if the results could not be recorded
*/
private HtmlEntry recordResolveAngularComponentTemplateTaskResults(
ResolveAngularComponentTemplateTask task) throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
HtmlEntry htmlEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof HtmlEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent an HTML file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-HTML file as an HTML file: "
+ source.getFullName());
}
htmlEntry = (HtmlEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (htmlEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
throw new AnalysisException("An HTML file became a non-HTML file: "
+ source.getFullName());
}
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null) {
htmlCopy.setValue(HtmlEntry.ANGULAR_ERRORS, task.getResolutionErrors());
// notify about errors
ChangeNoticeImpl notice = getNotice(source);
notice.setHtmlUnit(task.getResolvedUnit());
notice.setErrors(htmlCopy.getAllErrors(), htmlCopy.getValue(SourceEntry.LINE_INFO));
} else {
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
} else {
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// if (htmlCopy.getState(HtmlEntry.ANGULAR_ERRORS) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.ANGULAR_ERRORS, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.ELEMENT) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.ELEMENT, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.RESOLUTION_ERRORS) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.RESOLUTION_ERRORS, CacheState.INVALID);
// }
htmlCopy.invalidateAllInformation();
htmlCopy.setModificationTime(sourceTime);
cache.removedAst(source);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return htmlEntry;
}
/**
* Record the results produced by performing a {@link ResolveAngularEntryHtmlTask}. If the results
* were computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @throws AnalysisException if the results could not be recorded
*/
private HtmlEntry recordResolveAngularEntryHtmlTaskResults(ResolveAngularEntryHtmlTask task)
throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
HtmlEntry htmlEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof HtmlEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent an HTML file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-HTML file as an HTML file: "
+ source.getFullName());
}
htmlEntry = (HtmlEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (htmlEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
throw new AnalysisException("An HTML file became a non-HTML file: "
+ source.getFullName());
}
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null) {
htmlCopy.setValue(HtmlEntry.RESOLVED_UNIT, task.getResolvedUnit());
recordAngularEntryPoint(htmlCopy, task);
cache.storedAst(source);
ChangeNoticeImpl notice = getNotice(source);
notice.setHtmlUnit(task.getResolvedUnit());
notice.setErrors(htmlCopy.getAllErrors(), htmlCopy.getValue(SourceEntry.LINE_INFO));
} else {
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
} else {
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// if (htmlCopy.getState(HtmlEntry.ANGULAR_ERRORS) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.ANGULAR_ERRORS, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.ELEMENT) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.ELEMENT, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.RESOLUTION_ERRORS) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.RESOLUTION_ERRORS, CacheState.INVALID);
// }
htmlCopy.invalidateAllInformation();
htmlCopy.setModificationTime(sourceTime);
cache.removedAst(source);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return htmlEntry;
}
/**
* Record the results produced by performing a {@link ResolveDartUnitTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private DartEntry recordResolveDartUnitTaskResults(ResolveDartUnitTask task)
throws AnalysisException {
Source unitSource = task.getSource();
Source librarySource = task.getLibrarySource();
AnalysisException thrownException = task.getException();
DartEntry dartEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(unitSource);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(unitSource);
} else if (!(sourceEntry instanceof DartEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-Dart file as a Dart file: "
+ unitSource.getFullName());
}
dartEntry = (DartEntry) sourceEntry;
long sourceTime = getModificationStamp(unitSource);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (dartEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(unitSource);
dartEntry = getReadableDartEntry(unitSource);
if (dartEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ unitSource.getFullName());
}
}
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null) {
dartCopy.setValueInLibrary(DartEntry.RESOLVED_UNIT, librarySource, task.getResolvedUnit());
cache.storedAst(unitSource);
} else {
dartCopy.recordResolutionErrorInLibrary(librarySource, thrownException);
cache.removedAst(unitSource);
}
cache.put(unitSource, dartCopy);
dartEntry = dartCopy;
} else {
logInformation("Resolution results discarded for " + debuggingString(unitSource)
+ "; sourceTime = " + sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ dartEntry.getModificationTime(), thrownException);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// if (dartCopy.getState(DartEntry.RESOLVED_UNIT) == CacheState.IN_PROCESS) {
// dartCopy.setState(DartEntry.RESOLVED_UNIT, librarySource, CacheState.INVALID);
// }
removeFromParts(unitSource, dartEntry);
dartCopy.invalidateAllInformation();
dartCopy.setModificationTime(sourceTime);
cache.removedAst(unitSource);
workManager.add(unitSource, SourcePriority.UNKNOWN);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
dartCopy.recordResolutionErrorInLibrary(librarySource, thrownException);
}
cache.put(unitSource, dartCopy);
dartEntry = dartCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return dartEntry;
}
/**
* Record the results produced by performing a {@link ResolveHtmlTask}. If the results were
* computed from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private HtmlEntry recordResolveHtmlTaskResults(ResolveHtmlTask task) throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
HtmlEntry htmlEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof HtmlEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent an HTML file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to resolve non-HTML file as an HTML file: "
+ source.getFullName());
}
htmlEntry = (HtmlEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (htmlEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
htmlEntry = getReadableHtmlEntry(source);
if (htmlEntry == null) {
throw new AnalysisException("An HTML file became a non-HTML file: "
+ source.getFullName());
}
}
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null) {
htmlCopy.setState(HtmlEntry.PARSED_UNIT, CacheState.FLUSHED);
htmlCopy.setValue(HtmlEntry.RESOLVED_UNIT, task.getResolvedUnit());
htmlCopy.setValue(HtmlEntry.ELEMENT, task.getElement());
htmlCopy.setValue(HtmlEntry.RESOLUTION_ERRORS, task.getResolutionErrors());
cache.storedAst(source);
ChangeNoticeImpl notice = getNotice(source);
notice.setHtmlUnit(task.getResolvedUnit());
notice.setErrors(htmlCopy.getAllErrors(), htmlCopy.getValue(SourceEntry.LINE_INFO));
} else {
htmlCopy.recordResolutionError(thrownException);
cache.removedAst(source);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
} else {
logInformation("Resolution results discarded for " + debuggingString(source)
+ "; sourceTime = " + sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ htmlEntry.getModificationTime(), thrownException);
HtmlEntryImpl htmlCopy = htmlEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// if (htmlCopy.getState(HtmlEntry.ELEMENT) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.ELEMENT, CacheState.INVALID);
// }
// if (htmlCopy.getState(HtmlEntry.RESOLUTION_ERRORS) == CacheState.IN_PROCESS) {
// htmlCopy.setState(HtmlEntry.RESOLUTION_ERRORS, CacheState.INVALID);
// }
htmlCopy.invalidateAllInformation();
htmlCopy.setModificationTime(sourceTime);
cache.removedAst(source);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
htmlCopy.recordResolutionError(thrownException);
}
cache.put(source, htmlCopy);
htmlEntry = htmlCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return htmlEntry;
}
/**
* Record the results produced by performing a {@link ScanDartTask}. If the results were computed
* from data that is now out-of-date, then the results will not be recorded.
*
* @param task the task that was performed
* @return an entry containing the computed results
* @throws AnalysisException if the results could not be recorded
*/
private DartEntry recordScanDartTaskResults(ScanDartTask task) throws AnalysisException {
Source source = task.getSource();
AnalysisException thrownException = task.getException();
DartEntry dartEntry = null;
synchronized (cacheLock) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
throw new ObsoleteSourceAnalysisException(source);
} else if (!(sourceEntry instanceof DartEntry)) {
// This shouldn't be possible because we should never have performed the task if the source
// didn't represent a Dart file, but check to be safe.
throw new AnalysisException(
"Internal error: attempting to parse non-Dart file as a Dart file: "
+ source.getFullName());
}
dartEntry = (DartEntry) sourceEntry;
long sourceTime = getModificationStamp(source);
long resultTime = task.getModificationTime();
if (sourceTime == resultTime) {
if (dartEntry.getModificationTime() != sourceTime) {
// The source has changed without the context being notified. Simulate notification.
sourceChanged(source);
dartEntry = getReadableDartEntry(source);
if (dartEntry == null) {
throw new AnalysisException("A Dart file became a non-Dart file: "
+ source.getFullName());
}
}
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null) {
LineInfo lineInfo = task.getLineInfo();
dartCopy.setValue(SourceEntry.LINE_INFO, lineInfo);
dartCopy.setValue(DartEntry.TOKEN_STREAM, task.getTokenStream());
dartCopy.setValue(DartEntry.SCAN_ERRORS, task.getErrors());
cache.storedAst(source);
ChangeNoticeImpl notice = getNotice(source);
notice.setErrors(dartEntry.getAllErrors(), lineInfo);
} else {
removeFromParts(source, dartEntry);
dartCopy.recordScanError(thrownException);
cache.removedAst(source);
}
cache.put(source, dartCopy);
dartEntry = dartCopy;
} else {
logInformation(
"Scan results discarded for " + debuggingString(source) + "; sourceTime = "
+ sourceTime + ", resultTime = " + resultTime + ", cacheTime = "
+ dartEntry.getModificationTime(),
thrownException);
DartEntryImpl dartCopy = dartEntry.getWritableCopy();
if (thrownException == null || resultTime >= 0L) {
//
// The analysis was performed on out-of-date sources. Mark the cache so that the sources
// will be re-analyzed using the up-to-date sources.
//
// dartCopy.recordScanNotInProcess();
removeFromParts(source, dartEntry);
dartCopy.invalidateAllInformation();
dartCopy.setModificationTime(sourceTime);
cache.removedAst(source);
workManager.add(source, SourcePriority.UNKNOWN);
} else {
//
// We could not determine whether the sources were up-to-date or out-of-date. Mark the
// cache so that we won't attempt to re-analyze the sources until there's a good chance
// that we'll be able to do so without error.
//
dartCopy.recordScanError(thrownException);
}
cache.put(source, dartCopy);
dartEntry = dartCopy;
}
}
if (thrownException != null) {
throw thrownException;
}
return dartEntry;
}
/**
* Remove the given library from the list of containing libraries for all of the parts referenced
* by the given entry.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param librarySource the library to be removed
* @param dartEntry the entry containing the list of included parts
*/
private void removeFromParts(Source librarySource, DartEntry dartEntry) {
Source[] oldParts = dartEntry.getValue(DartEntry.INCLUDED_PARTS);
for (int i = 0; i < oldParts.length; i++) {
Source partSource = oldParts[i];
DartEntry partEntry = getReadableDartEntry(partSource);
if (partEntry != null && partEntry != dartEntry) {
DartEntryImpl partCopy = partEntry.getWritableCopy();
partCopy.removeContainingLibrary(librarySource);
if (partCopy.getContainingLibraries().size() == 0 && !exists(partSource)) {
cache.remove(partSource);
} else {
cache.put(partSource, partCopy);
}
}
}
}
/**
* Remove the given libraries that are keys in the given map from the list of containing libraries
* for each of the parts in the corresponding value.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param oldPartMap the table containing the parts associated with each library
*/
private void removeFromPartsUsingMap(HashMap<Source, Source[]> oldPartMap) {
for (Map.Entry<Source, Source[]> entry : oldPartMap.entrySet()) {
Source librarySource = entry.getKey();
Source[] oldParts = entry.getValue();
for (int i = 0; i < oldParts.length; i++) {
Source partSource = oldParts[i];
if (!partSource.equals(librarySource)) {
DartEntry partEntry = getReadableDartEntry(partSource);
if (partEntry != null) {
DartEntryImpl partCopy = partEntry.getWritableCopy();
partCopy.removeContainingLibrary(librarySource);
if (partCopy.getContainingLibraries().size() == 0 && !exists(partSource)) {
cache.remove(partSource);
} else {
cache.put(partSource, partCopy);
}
}
}
}
}
}
/**
* Remove the given source from the priority order if it is in the list.
*
* @param source the source to be removed
*/
private void removeFromPriorityOrder(Source source) {
int count = priorityOrder.length;
ArrayList<Source> newOrder = new ArrayList<Source>(count);
for (int i = 0; i < count; i++) {
if (!priorityOrder[i].equals(source)) {
newOrder.add(priorityOrder[i]);
}
}
if (newOrder.size() < count) {
setAnalysisPriorityOrder(newOrder);
}
}
/**
* Create an entry for the newly added source. Return {@code true} if the new source is a Dart
* file.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param source the source that has been added
* @return {@code true} if the new source is a Dart file
*/
private boolean sourceAvailable(Source source) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null) {
sourceEntry = createSourceEntry(source, true);
} else {
sourceChanged(source);
sourceEntry = cache.get(source);
}
if (sourceEntry instanceof HtmlEntry) {
workManager.add(source, SourcePriority.HTML);
} else if (sourceEntry instanceof DartEntry) {
workManager.add(source, computePriority((DartEntry) sourceEntry));
}
return sourceEntry instanceof DartEntry;
}
/**
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param source the source that has been changed
*/
private void sourceChanged(Source source) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry == null || sourceEntry.getModificationTime() == getModificationStamp(source)) {
// Either we have removed this source, in which case we don't care that it is changed, or we
// have already invalidated the cache and don't need to invalidate it again.
return;
}
if (sourceEntry instanceof HtmlEntry) {
HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy();
htmlCopy.setModificationTime(getModificationStamp(source));
invalidateAngularResolution(htmlCopy);
htmlCopy.invalidateAllInformation();
cache.put(source, htmlCopy);
cache.removedAst(source);
workManager.add(source, SourcePriority.HTML);
} else if (sourceEntry instanceof DartEntry) {
Source[] containingLibraries = getLibrariesContaining(source);
HashSet<Source> librariesToInvalidate = new HashSet<Source>();
for (Source containingLibrary : containingLibraries) {
computeAllLibrariesDependingOn(containingLibrary, librariesToInvalidate);
}
for (Source library : librariesToInvalidate) {
invalidateLibraryResolution(library);
}
removeFromParts(source, ((DartEntry) cache.get(source)));
DartEntryImpl dartCopy = ((DartEntry) cache.get(source)).getWritableCopy();
dartCopy.setModificationTime(getModificationStamp(source));
dartCopy.invalidateAllInformation();
cache.put(source, dartCopy);
cache.removedAst(source);
workManager.add(source, SourcePriority.UNKNOWN);
}
}
/**
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param source the source that has been deleted
*/
private void sourceDeleted(Source source) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry instanceof HtmlEntry) {
HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy();
invalidateAngularResolution(htmlCopy);
htmlCopy.recordContentError(new AnalysisException("This source was marked as being deleted"));
cache.put(source, htmlCopy);
} else if (sourceEntry instanceof DartEntry) {
HashSet<Source> libraries = new HashSet<Source>();
for (Source librarySource : getLibrariesContaining(source)) {
libraries.add(librarySource);
for (Source dependentLibrary : getLibrariesDependingOn(librarySource)) {
libraries.add(dependentLibrary);
}
}
for (Source librarySource : libraries) {
invalidateLibraryResolution(librarySource);
}
DartEntryImpl dartCopy = ((DartEntry) sourceEntry).getWritableCopy();
dartCopy.recordContentError(new AnalysisException("This source was marked as being deleted"));
cache.put(source, dartCopy);
}
workManager.remove(source);
removeFromPriorityOrder(source);
}
/**
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @param source the source that has been removed
*/
private void sourceRemoved(Source source) {
SourceEntry sourceEntry = cache.get(source);
if (sourceEntry instanceof HtmlEntry) {
HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy();
invalidateAngularResolution(htmlCopy);
} else if (sourceEntry instanceof DartEntry) {
HashSet<Source> libraries = new HashSet<Source>();
for (Source librarySource : getLibrariesContaining(source)) {
libraries.add(librarySource);
for (Source dependentLibrary : getLibrariesDependingOn(librarySource)) {
libraries.add(dependentLibrary);
}
}
for (Source librarySource : libraries) {
invalidateLibraryResolution(librarySource);
}
}
cache.remove(source);
workManager.remove(source);
removeFromPriorityOrder(source);
}
/**
* Check the cache for any invalid entries (entries whose modification time does not match the
* modification time of the source associated with the entry). Invalid entries will be marked as
* invalid so that the source will be re-analyzed.
* <p>
* <b>Note:</b> This method must only be invoked while we are synchronized on {@link #cacheLock}.
*
* @return {@code true} if at least one entry was invalid
*/
private boolean validateCacheConsistency() {
long consistencyCheckStart = System.nanoTime();
ArrayList<Source> changedSources = new ArrayList<Source>();
ArrayList<Source> missingSources = new ArrayList<Source>();
synchronized (cacheLock) {
MapIterator<Source, SourceEntry> iterator = cache.iterator();
while (iterator.moveNext()) {
Source source = iterator.getKey();
SourceEntry sourceEntry = iterator.getValue();
long sourceTime = getModificationStamp(source);
if (sourceTime != sourceEntry.getModificationTime()) {
changedSources.add(source);
}
if (sourceEntry.getException() != null) {
if (!exists(source)) {
missingSources.add(source);
}
}
}
int count = changedSources.size();
for (int i = 0; i < count; i++) {
sourceChanged(changedSources.get(i));
}
}
long consistencyCheckEnd = System.nanoTime();
if (changedSources.size() > 0 || missingSources.size() > 0) {
@SuppressWarnings("resource")
PrintStringWriter writer = new PrintStringWriter();
writer.print("Consistency check took ");
writer.print((consistencyCheckEnd - consistencyCheckStart) / 1000000.0);
writer.println(" ms and found");
writer.print(" ");
writer.print(changedSources.size());
writer.println(" inconsistent entries");
writer.print(" ");
writer.print(missingSources.size());
writer.println(" missing sources");
for (Source source : missingSources) {
writer.print(" ");
writer.println(source.getFullName());
}
logInformation(writer.toString());
}
return changedSources.size() > 0;
}
}