/*
* Copyright (c) 2013, 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.resolver;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.Directive;
import com.google.dart.engine.ast.LibraryDirective;
import com.google.dart.engine.ast.LibraryIdentifier;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.PartDirective;
import com.google.dart.engine.ast.PartOfDirective;
import com.google.dart.engine.ast.StringLiteral;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.FunctionElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.error.CompileTimeErrorCode;
import com.google.dart.engine.error.StaticWarningCode;
import com.google.dart.engine.internal.builder.CompilationUnitBuilder;
import com.google.dart.engine.internal.context.InternalAnalysisContext;
import com.google.dart.engine.internal.element.CompilationUnitElementImpl;
import com.google.dart.engine.internal.element.LibraryElementImpl;
import com.google.dart.engine.internal.element.PropertyAccessorElementImpl;
import com.google.dart.engine.internal.element.PropertyInducingElementImpl;
import com.google.dart.engine.resolver.ResolverErrorCode;
import com.google.dart.engine.source.Source;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Instances of the class {@code LibraryElementBuilder} build an element model for a single library.
*
* @coverage dart.engine.resolver
*/
public class LibraryElementBuilder {
/**
* The analysis context in which the element model will be built.
*/
private InternalAnalysisContext analysisContext;
/**
* The listener to which errors will be reported.
*/
private AnalysisErrorListener errorListener;
/**
* The name of the function used as an entry point.
*/
public static final String ENTRY_POINT_NAME = "main";
/**
* Initialize a newly created library element builder.
*
* @param analysisContext the analysis context in which the element model will be built
* @param errorListener the listener to which errors will be reported
*/
public LibraryElementBuilder(InternalAnalysisContext analysisContext,
AnalysisErrorListener errorListener) {
this.analysisContext = analysisContext;
this.errorListener = errorListener;
}
/**
* Build the library element for the given library.
*
* @param library the library for which an element model is to be built
* @return the library element that was built
* @throws AnalysisException if the analysis could not be performed
*/
public LibraryElementImpl buildLibrary(Library library) throws AnalysisException {
CompilationUnitBuilder builder = new CompilationUnitBuilder();
Source librarySource = library.getLibrarySource();
CompilationUnit definingCompilationUnit = library.getDefiningCompilationUnit();
CompilationUnitElementImpl definingCompilationUnitElement = builder.buildCompilationUnit(
librarySource,
definingCompilationUnit);
NodeList<Directive> directives = definingCompilationUnit.getDirectives();
LibraryIdentifier libraryNameNode = null;
boolean hasPartDirective = false;
FunctionElement entryPoint = findEntryPoint(definingCompilationUnitElement);
ArrayList<Directive> directivesToResolve = new ArrayList<Directive>();
ArrayList<CompilationUnitElementImpl> sourcedCompilationUnits = new ArrayList<CompilationUnitElementImpl>();
for (Directive directive : directives) {
//
// We do not build the elements representing the import and export directives at this point.
// That is not done until we get to LibraryResolver.buildDirectiveModels() because we need the
// LibraryElements for the referenced libraries, which might not exist at this point (due to
// the possibility of circular references).
//
if (directive instanceof LibraryDirective) {
if (libraryNameNode == null) {
libraryNameNode = ((LibraryDirective) directive).getName();
directivesToResolve.add(directive);
}
} else if (directive instanceof PartDirective) {
PartDirective partDirective = (PartDirective) directive;
StringLiteral partUri = partDirective.getUri();
Source partSource = partDirective.getSource();
if (analysisContext.exists(partSource)) {
hasPartDirective = true;
CompilationUnit partUnit = library.getAST(partSource);
CompilationUnitElementImpl part = builder.buildCompilationUnit(partSource, partUnit);
part.setUriOffset(partUri.getOffset());
part.setUriEnd(partUri.getEnd());
part.setUri(partDirective.getUriContent());
//
// Validate that the part contains a part-of directive with the same name as the library.
//
String partLibraryName = getPartLibraryName(partSource, partUnit, directivesToResolve);
if (partLibraryName == null) {
errorListener.onError(new AnalysisError(
librarySource,
partUri.getOffset(),
partUri.getLength(),
CompileTimeErrorCode.PART_OF_NON_PART,
partUri.toSource()));
} else if (libraryNameNode == null) {
// TODO(brianwilkerson) Collect the names declared by the part. If they are all the same
// then we can use that name as the inferred name of the library and present it in a
// quick-fix.
// partLibraryNames.add(partLibraryName);
} else if (!libraryNameNode.getName().equals(partLibraryName)) {
errorListener.onError(new AnalysisError(
librarySource,
partUri.getOffset(),
partUri.getLength(),
StaticWarningCode.PART_OF_DIFFERENT_LIBRARY,
libraryNameNode.getName(),
partLibraryName));
}
if (entryPoint == null) {
entryPoint = findEntryPoint(part);
}
directive.setElement(part);
sourcedCompilationUnits.add(part);
}
}
}
if (hasPartDirective && libraryNameNode == null) {
errorListener.onError(new AnalysisError(
librarySource,
ResolverErrorCode.MISSING_LIBRARY_DIRECTIVE_WITH_PART));
}
//
// Create and populate the library element.
//
LibraryElementImpl libraryElement = new LibraryElementImpl(
analysisContext.getContextFor(librarySource),
libraryNameNode);
libraryElement.setDefiningCompilationUnit(definingCompilationUnitElement);
if (entryPoint != null) {
libraryElement.setEntryPoint(entryPoint);
}
int sourcedUnitCount = sourcedCompilationUnits.size();
libraryElement.setParts(sourcedCompilationUnits.toArray(new CompilationUnitElementImpl[sourcedUnitCount]));
for (Directive directive : directivesToResolve) {
directive.setElement(libraryElement);
}
library.setLibraryElement(libraryElement);
if (sourcedUnitCount > 0) {
patchTopLevelAccessors(libraryElement);
}
return libraryElement;
}
/**
* Build the library element for the given library.
*
* @param library the library for which an element model is to be built
* @return the library element that was built
* @throws AnalysisException if the analysis could not be performed
*/
public LibraryElementImpl buildLibrary(ResolvableLibrary library) throws AnalysisException {
CompilationUnitBuilder builder = new CompilationUnitBuilder();
Source librarySource = library.getLibrarySource();
CompilationUnit definingCompilationUnit = library.getDefiningCompilationUnit();
CompilationUnitElementImpl definingCompilationUnitElement = builder.buildCompilationUnit(
librarySource,
definingCompilationUnit);
NodeList<Directive> directives = definingCompilationUnit.getDirectives();
LibraryIdentifier libraryNameNode = null;
boolean hasPartDirective = false;
FunctionElement entryPoint = findEntryPoint(definingCompilationUnitElement);
ArrayList<Directive> directivesToResolve = new ArrayList<Directive>();
ArrayList<CompilationUnitElementImpl> sourcedCompilationUnits = new ArrayList<CompilationUnitElementImpl>();
for (Directive directive : directives) {
//
// We do not build the elements representing the import and export directives at this point.
// That is not done until we get to LibraryResolver.buildDirectiveModels() because we need the
// LibraryElements for the referenced libraries, which might not exist at this point (due to
// the possibility of circular references).
//
if (directive instanceof LibraryDirective) {
if (libraryNameNode == null) {
libraryNameNode = ((LibraryDirective) directive).getName();
directivesToResolve.add(directive);
}
} else if (directive instanceof PartDirective) {
PartDirective partDirective = (PartDirective) directive;
StringLiteral partUri = partDirective.getUri();
Source partSource = partDirective.getSource();
if (analysisContext.exists(partSource)) {
hasPartDirective = true;
CompilationUnit partUnit = library.getAST(partSource);
if (partUnit != null) {
CompilationUnitElementImpl part = builder.buildCompilationUnit(partSource, partUnit);
part.setUriOffset(partUri.getOffset());
part.setUriEnd(partUri.getEnd());
part.setUri(partDirective.getUriContent());
//
// Validate that the part contains a part-of directive with the same name as the library.
//
String partLibraryName = getPartLibraryName(partSource, partUnit, directivesToResolve);
if (partLibraryName == null) {
errorListener.onError(new AnalysisError(
librarySource,
partUri.getOffset(),
partUri.getLength(),
CompileTimeErrorCode.PART_OF_NON_PART,
partUri.toSource()));
} else if (libraryNameNode == null) {
// TODO(brianwilkerson) Collect the names declared by the part. If they are all the same
// then we can use that name as the inferred name of the library and present it in a
// quick-fix.
// partLibraryNames.add(partLibraryName);
} else if (!libraryNameNode.getName().equals(partLibraryName)) {
errorListener.onError(new AnalysisError(
librarySource,
partUri.getOffset(),
partUri.getLength(),
StaticWarningCode.PART_OF_DIFFERENT_LIBRARY,
libraryNameNode.getName(),
partLibraryName));
}
if (entryPoint == null) {
entryPoint = findEntryPoint(part);
}
directive.setElement(part);
sourcedCompilationUnits.add(part);
}
}
}
}
if (hasPartDirective && libraryNameNode == null) {
errorListener.onError(new AnalysisError(
librarySource,
ResolverErrorCode.MISSING_LIBRARY_DIRECTIVE_WITH_PART));
}
//
// Create and populate the library element.
//
LibraryElementImpl libraryElement = new LibraryElementImpl(
analysisContext.getContextFor(librarySource),
libraryNameNode);
libraryElement.setDefiningCompilationUnit(definingCompilationUnitElement);
if (entryPoint != null) {
libraryElement.setEntryPoint(entryPoint);
}
int sourcedUnitCount = sourcedCompilationUnits.size();
libraryElement.setParts(sourcedCompilationUnits.toArray(new CompilationUnitElementImpl[sourcedUnitCount]));
for (Directive directive : directivesToResolve) {
directive.setElement(libraryElement);
}
library.setLibraryElement(libraryElement);
if (sourcedUnitCount > 0) {
patchTopLevelAccessors(libraryElement);
}
return libraryElement;
}
/**
* Add all of the non-synthetic getters and setters defined in the given compilation unit that
* have no corresponding accessor to one of the given collections.
*
* @param getters the map to which getters are to be added
* @param setters the list to which setters are to be added
* @param unit the compilation unit defining the accessors that are potentially being added
*/
private void collectAccessors(HashMap<String, PropertyAccessorElement> getters,
ArrayList<PropertyAccessorElement> setters, CompilationUnitElement unit) {
for (PropertyAccessorElement accessor : unit.getAccessors()) {
if (accessor.isGetter()) {
if (!accessor.isSynthetic() && accessor.getCorrespondingSetter() == null) {
getters.put(accessor.getDisplayName(), accessor);
}
} else {
if (!accessor.isSynthetic() && accessor.getCorrespondingGetter() == null) {
setters.add(accessor);
}
}
}
}
/**
* Search the top-level functions defined in the given compilation unit for the entry point.
*
* @param element the compilation unit to be searched
* @return the entry point that was found, or {@code null} if the compilation unit does not define
* an entry point
*/
private FunctionElement findEntryPoint(CompilationUnitElementImpl element) {
for (FunctionElement function : element.getFunctions()) {
if (function.getName().equals(ENTRY_POINT_NAME)) {
return function;
}
}
return null;
}
/**
* Return the name of the library that the given part is declared to be a part of, or {@code null}
* if the part does not contain a part-of directive.
*
* @param partSource the source representing the part
* @param partUnit the AST structure of the part
* @param directivesToResolve a list of directives that should be resolved to the library being
* built
* @return the name of the library that the given part is declared to be a part of
*/
private String getPartLibraryName(Source partSource, CompilationUnit partUnit,
ArrayList<Directive> directivesToResolve) {
for (Directive directive : partUnit.getDirectives()) {
if (directive instanceof PartOfDirective) {
directivesToResolve.add(directive);
LibraryIdentifier libraryName = ((PartOfDirective) directive).getLibraryName();
if (libraryName != null) {
return libraryName.getName();
}
}
}
return null;
}
/**
* Look through all of the compilation units defined for the given library, looking for getters
* and setters that are defined in different compilation units but that have the same names. If
* any are found, make sure that they have the same variable element.
*
* @param libraryElement the library defining the compilation units to be processed
*/
private void patchTopLevelAccessors(LibraryElementImpl libraryElement) {
HashMap<String, PropertyAccessorElement> getters = new HashMap<String, PropertyAccessorElement>();
ArrayList<PropertyAccessorElement> setters = new ArrayList<PropertyAccessorElement>();
collectAccessors(getters, setters, libraryElement.getDefiningCompilationUnit());
for (CompilationUnitElement unit : libraryElement.getParts()) {
collectAccessors(getters, setters, unit);
}
for (PropertyAccessorElement setter : setters) {
PropertyAccessorElement getter = getters.get(setter.getDisplayName());
if (getter != null) {
PropertyInducingElementImpl variable = (PropertyInducingElementImpl) getter.getVariable();
variable.setSetter(setter);
((PropertyAccessorElementImpl) setter).setVariable(variable);
}
}
}
}