/*
* 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.services.util;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.ClassMemberElement;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.FieldElement;
import com.google.dart.engine.element.HtmlElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.visitor.GeneralizingElementVisitor;
import com.google.dart.engine.search.SearchEngine;
import com.google.dart.engine.search.SearchMatch;
import com.google.dart.engine.services.internal.correction.CorrectionUtils;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.utilities.translation.DartOmit;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Helper for {@link ClassElement} hierarchy.
*/
@DartOmit
public class HierarchyUtils {
public static List<SearchMatch> getAccessibleMatches(Element element, List<SearchMatch> matches) {
Map<LibraryElement, Set<LibraryElement>> cachedVisibleLibraries = Maps.newHashMap();
// just search for name
if (element == null) {
return matches;
}
LibraryElement elementLibrary = element.getLibrary();
// prepare filtered matches
List<SearchMatch> filteredMatches = Lists.newArrayList();
for (SearchMatch match : matches) {
Element matchElement = match.getElement();
// HtmlElement has no enclosing LibraryElement to check, so always keep these matches
if (matchElement instanceof HtmlElement) {
HtmlElement htmlElement = (HtmlElement) matchElement;
CompilationUnitElement angularUnit = htmlElement.getAngularCompilationUnit();
if (angularUnit == null) {
continue;
}
matchElement = angularUnit;
}
// check enclosing LibraryElement
LibraryElement matchLibrary = matchElement.getLibrary();
if (isImported(cachedVisibleLibraries, elementLibrary, matchLibrary)) {
filteredMatches.add(match);
}
}
// done
return filteredMatches;
}
/**
* @return direct non-synthetic members of the given {@link ClassElement}. This includes fields,
* accessors (if not synthetic). Does not include constructors.
*/
public static List<Element> getDirectMembers(final ClassElement clazz,
final boolean includeSynthetic) {
final List<Element> members = Lists.newArrayList();
clazz.accept(new GeneralizingElementVisitor<Void>() {
@Override
public Void visitElement(Element element) {
if (element == clazz) {
return super.visitElement(element);
}
if (!includeSynthetic && element.isSynthetic()) {
return null;
}
if (element instanceof ConstructorElement) {
return null;
}
if (element instanceof ExecutableElement) {
members.add(element);
}
if (element instanceof FieldElement) {
members.add(element);
}
return null;
}
});
return members;
}
/**
* @return the {@link List} with direct sub {@link ClassElement}s of the given.
*/
public static List<ClassElement> getDirectSubClasses(SearchEngine searchEngine, ClassElement seed) {
List<ClassElement> subClasses = Lists.newArrayList();
// ask SearchEngine
List<SearchMatch> subMatches = searchEngine.searchSubtypes(seed, null, null);
for (SearchMatch subMatch : subMatches) {
ClassElement subClass = (ClassElement) subMatch.getElement();
subClasses.add(subClass);
}
// done
return subClasses;
}
/**
* @return all implementations of the given {@link ClassMemberElement} is its superclasses and
* their subclasses.
*/
public static Set<ClassMemberElement> getHierarchyMembers(SearchEngine searchEngine,
ClassMemberElement member) {
if (member instanceof ConstructorElement) {
return Sets.newHashSet(member);
}
String name = member.getName();
ClassElement memberClass = member.getEnclosingElement();
Set<ClassElement> superClasses = getSuperClasses(memberClass);
superClasses.add(memberClass);
Set<ClassMemberElement> result = Sets.newHashSet();
for (ClassElement superClass : superClasses) {
// ignore if super- class does not declare member
if (CorrectionUtils.getChildren(superClass, name).isEmpty()) {
continue;
}
// check all sub- classes
Set<ClassElement> subClasses = getSubClasses(searchEngine, superClass);
subClasses.add(superClass);
for (ClassElement subClass : subClasses) {
List<Element> subClassMembers = CorrectionUtils.getChildren(subClass, name);
// add "name" children/member(s)
for (Element subClassMember : subClassMembers) {
if (subClassMember instanceof ClassMemberElement) {
result.add((ClassMemberElement) subClassMember);
}
}
}
}
return result;
}
/**
* @return non-synthetic members of the given {@link ClassElement} and its super classes. This
* includes fields, accessors (if not synthetic), method. Does not include constructors.
*/
public static List<Element> getMembers(ClassElement clazz, boolean includeSynthetic) {
List<Element> members = Lists.newArrayList();
members.addAll(getDirectMembers(clazz, includeSynthetic));
Set<ClassElement> superClasses = getSuperClasses(clazz);
for (ClassElement superClass : superClasses) {
members.addAll(getDirectMembers(superClass, includeSynthetic));
}
return members;
}
/**
* @return the {@link Set} with all direct and indirect sub {@link ClassElement}s of the given.
*/
public static Set<ClassElement> getSubClasses(SearchEngine searchEngine, ClassElement seed) {
Set<ClassElement> subClasses = Sets.newHashSet();
// prepare queue
LinkedList<ClassElement> subClassQueue = Lists.newLinkedList();
subClassQueue.add(seed);
// process queue
while (!subClassQueue.isEmpty()) {
ClassElement subClass = subClassQueue.removeFirst();
if (subClasses.add(subClass)) {
List<ClassElement> directSubClasses = getDirectSubClasses(searchEngine, subClass);
subClassQueue.addAll(directSubClasses);
}
}
// we don't need "seed" itself
subClasses.remove(seed);
return subClasses;
}
/**
* @return the {@link Set} with all direct and indirect super {@link ClassElement}s of the given.
*/
public static Set<ClassElement> getSuperClasses(ClassElement seed) {
Set<ClassElement> result = Sets.newHashSet();
// prepare queue
LinkedList<ClassElement> queue = Lists.newLinkedList();
queue.add(seed);
// process queue
while (!queue.isEmpty()) {
ClassElement current = queue.removeFirst();
// add if not checked already
if (!result.add(current)) {
continue;
}
// append supertype
{
InterfaceType superType = current.getSupertype();
if (superType != null) {
queue.add(superType.getElement());
}
}
// append interfaces
for (InterfaceType intf : current.getInterfaces()) {
queue.add(intf.getElement());
}
}
// we don't need "seed" itself
result.remove(seed);
return result;
}
public static Element getSyntheticAccessorVariable(Element element) {
if (element instanceof PropertyAccessorElement) {
PropertyAccessorElement accessor = (PropertyAccessorElement) element;
if (accessor.isSynthetic()) {
element = accessor.getVariable();
}
}
return element;
}
/**
* Checks if "what" is imported into "where" directly or indirectly, so there is a chance that it
* has access to an object from "what". Otherwise we find too many "second-order" positive
* matches.
* <p>
* https://code.google.com/p/dart/issues/detail?id=12268
*/
private static boolean isImported(
Map<LibraryElement, Set<LibraryElement>> cachedVisibleLibraries, LibraryElement what,
LibraryElement where) {
Set<LibraryElement> visibleLibraries = cachedVisibleLibraries.get(where);
if (visibleLibraries == null) {
LibraryElement[] visibleLibrariesArray = where.getVisibleLibraries();
visibleLibraries = ImmutableSet.copyOf(visibleLibrariesArray);
cachedVisibleLibraries.put(where, visibleLibraries);
}
return visibleLibraries.contains(what);
}
}