/* * 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.scope; import com.google.dart.engine.AnalysisEngine; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.Identifier; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ImportElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.error.AnalysisError; import com.google.dart.engine.error.AnalysisErrorListener; import com.google.dart.engine.error.StaticWarningCode; import com.google.dart.engine.internal.element.MultiplyDefinedElementImpl; import com.google.dart.engine.source.Source; import com.google.dart.engine.utilities.general.StringUtilities; import java.util.ArrayList; import java.util.Arrays; /** * Instances of the class {@code LibraryImportScope} represent the scope containing all of the names * available from imported libraries. * * @coverage dart.engine.resolver */ public class LibraryImportScope extends Scope { /** * The element representing the library in which this scope is enclosed. */ private LibraryElement definingLibrary; /** * The listener that is to be informed when an error is encountered. */ private AnalysisErrorListener errorListener; /** * A list of the namespaces representing the names that are available in this scope from imported * libraries. */ private Namespace[] importedNamespaces; /** * Initialize a newly created scope representing the names imported into the given library. * * @param definingLibrary the element representing the library that imports the names defined in * this scope * @param errorListener the listener that is to be informed when an error is encountered */ public LibraryImportScope(LibraryElement definingLibrary, AnalysisErrorListener errorListener) { this.definingLibrary = definingLibrary; this.errorListener = errorListener; createImportedNamespaces(); } @Override public void define(Element element) { if (!isPrivateName(element.getDisplayName())) { super.define(element); } } @Override public AnalysisErrorListener getErrorListener() { return errorListener; } @Override protected Source getSource(AstNode node) { Source source = super.getSource(node); if (source == null) { source = definingLibrary.getDefiningCompilationUnit().getSource(); } return source; } @Override protected Element internalLookup(Identifier identifier, String name, LibraryElement referencingLibrary) { Element foundElement = localLookup(name, referencingLibrary); if (foundElement != null) { return foundElement; } for (int i = 0; i < importedNamespaces.length; i++) { Namespace nameSpace = importedNamespaces[i]; Element element = nameSpace.get(name); if (element != null) { if (foundElement == null) { foundElement = element; } else if (foundElement != element) { foundElement = MultiplyDefinedElementImpl.fromElements( definingLibrary.getContext(), foundElement, element); } } } if (foundElement instanceof MultiplyDefinedElementImpl) { foundElement = removeSdkElements(identifier, name, (MultiplyDefinedElementImpl) foundElement); } if (foundElement instanceof MultiplyDefinedElementImpl) { String foundEltName = foundElement.getDisplayName(); Element[] conflictingMembers = ((MultiplyDefinedElementImpl) foundElement).getConflictingElements(); int count = conflictingMembers.length; String[] libraryNames = new String[count]; for (int i = 0; i < count; i++) { libraryNames[i] = getLibraryName(conflictingMembers[i]); } Arrays.sort(libraryNames); errorListener.onError(new AnalysisError( getSource(identifier), identifier.getOffset(), identifier.getLength(), StaticWarningCode.AMBIGUOUS_IMPORT, foundEltName, StringUtilities.printListOfQuotedNames(libraryNames))); return foundElement; } if (foundElement != null) { defineNameWithoutChecking(name, foundElement); } return foundElement; } /** * Create all of the namespaces associated with the libraries imported into this library. The * names are not added to this scope, but are stored for later reference. * * @param definingLibrary the element representing the library that imports the libraries for * which namespaces will be created */ private final void createImportedNamespaces() { NamespaceBuilder builder = new NamespaceBuilder(); ImportElement[] imports = definingLibrary.getImports(); int count = imports.length; importedNamespaces = new Namespace[count]; for (int i = 0; i < count; i++) { importedNamespaces[i] = builder.createImportNamespaceForDirective(imports[i]); } } /** * Returns the name of the library that defines given element. * * @param element the element to get library name * @return the name of the library that defines given element */ private String getLibraryName(Element element) { if (element == null) { return StringUtilities.EMPTY; } LibraryElement library = element.getLibrary(); if (library == null) { return StringUtilities.EMPTY; } ImportElement[] imports = definingLibrary.getImports(); int count = imports.length; for (int i = 0; i < count; i++) { if (imports[i].getImportedLibrary() == library) { return library.getDefiningCompilationUnit().getDisplayName(); } } ArrayList<String> indirectSources = new ArrayList<String>(); for (int i = 0; i < count; i++) { LibraryElement importedLibrary = imports[i].getImportedLibrary(); if (importedLibrary != null) { for (LibraryElement exportedLibrary : importedLibrary.getExportedLibraries()) { if (exportedLibrary == library) { indirectSources.add(importedLibrary.getDefiningCompilationUnit().getDisplayName()); } } } } int indirectCount = indirectSources.size(); StringBuilder builder = new StringBuilder(); builder.append(library.getDefiningCompilationUnit().getDisplayName()); if (indirectCount > 0) { builder.append(" (via "); if (indirectCount > 1) { String[] indirectNames = indirectSources.toArray(new String[indirectCount]); Arrays.sort(indirectNames); builder.append(StringUtilities.printListOfQuotedNames(indirectNames)); } else { builder.append(indirectSources.get(0)); } builder.append(")"); } return builder.toString(); } /** * Given a collection of elements that a single name could all be mapped to, remove from the list * all of the names defined in the SDK. Return the element(s) that remain. * * @param identifier the identifier node to lookup element for, used to report correct kind of a * problem and associate problem with * @param name the name associated with the element * @param foundElement the element encapsulating the collection of elements * @return all of the elements that are not defined in the SDK */ private Element removeSdkElements(Identifier identifier, String name, MultiplyDefinedElementImpl foundElement) { Element[] conflictingMembers = foundElement.getConflictingElements(); int length = conflictingMembers.length; int to = 0; Element sdkElement = null; for (Element member : conflictingMembers) { if (member.getLibrary().isInSdk()) { sdkElement = member; } else { conflictingMembers[to++] = member; } } if (sdkElement != null && to > 0) { String sdkLibName = getLibraryName(sdkElement); String otherLibName = getLibraryName(conflictingMembers[0]); errorListener.onError(new AnalysisError( getSource(identifier), identifier.getOffset(), identifier.getLength(), StaticWarningCode.CONFLICTING_DART_IMPORT, name, sdkLibName, otherLibName)); } if (to == length) { // None of the members were removed return foundElement; } else if (to == 1) { // All but one member was removed return conflictingMembers[0]; } else if (to == 0) { // All members were removed AnalysisEngine.getInstance().getLogger().logInformation( "Multiply defined SDK element: " + foundElement); return foundElement; } Element[] remaining = new Element[to]; System.arraycopy(conflictingMembers, 0, remaining, 0, to); return new MultiplyDefinedElementImpl(definingLibrary.getContext(), remaining); } }