/* * 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; } }