blob: cdbcc428cdc35b34e17b529952a4db963f19660d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 NumberFour AG
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* NumberFour AG - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.javascript.typeinfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.annotations.Internal;
import org.eclipse.dltk.javascript.typeinfo.model.JSType;
import org.eclipse.dltk.javascript.typeinfo.model.Member;
import org.eclipse.dltk.javascript.typeinfo.model.Method;
import org.eclipse.dltk.javascript.typeinfo.model.Parameter;
import org.eclipse.dltk.javascript.typeinfo.model.Type;
import org.eclipse.dltk.javascript.typeinfo.model.TypeKind;
import org.eclipse.dltk.utils.CompoundIterator;
import org.eclipse.emf.common.util.EList;
/**
* Returns all the members defined by the specified type(s) and all it's super
* types and implemented traits.
*
* Types are visited in the breadth-first order (current type, super type,
* traits), so the overriding methods are visited first. By default overridden
* methods are returned multiple times, if you want to skip duplicates and have
* only the actual implementations then use the {@link Iterable} returned by
* {@link #ignoreDuplicates()}.
*/
public class TypeMemberQuery implements Iterable<Member> {
private static class QueueItem {
final Type type;
final MemberPredicate predicate;
public QueueItem(Type type, MemberPredicate predicate) {
this.type = type;
this.predicate = predicate;
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof QueueItem) {
final QueueItem other = (QueueItem) obj;
return type.equals(other.type)
&& predicate.equals(other.predicate);
}
return false;
}
@Override
public String toString() {
return predicate + ":" + type.getName();
}
}
@Internal
final List<QueueItem> types = new ArrayList<QueueItem>();
/**
* Creates empty query
*/
public TypeMemberQuery() {
}
/**
* Creates query for all the members of the specified type.
*/
public TypeMemberQuery(Type type) {
add(type);
}
/**
* Creates query for the members of the specified type matching the
* specified predicate.
*
* @see MemberPredicates
*/
public TypeMemberQuery(Type type, MemberPredicate predicate) {
add(type, predicate);
}
/**
* Adds the specified type to this query
*/
public void add(Type type) {
add(type, MemberPredicates.ALWAYS_TRUE);
}
/**
* Adds the specified type with the specified predicate to this query.
*/
public void add(Type type, MemberPredicate predicate) {
Assert.isNotNull(type);
types.add(new QueueItem(type, predicate));
}
protected boolean isValid(Type type) {
return true;
}
private class TypeIterator extends CompoundIterator<QueueItem> {
private final Set<QueueItem> visited = new HashSet<QueueItem>();
private final List<QueueItem> queue = new ArrayList<QueueItem>();
public TypeIterator(int mode) {
if (mode == ALL) {
queue.addAll(types);
} else {
assert mode == SUPERTYPES;
for (QueueItem qi : types) {
final Type type = qi.type;
final Type superType = type.getSuperType();
if (superType != null) {
queue.add(new QueueItem(superType, qi.predicate));
}
for (Type trait : type.getTraits()) {
queue.add(new QueueItem(trait, qi.predicate));
}
}
}
current = queue.iterator();
}
private boolean canVisit(QueueItem item) {
return visited.add(item);
}
@Override
protected boolean fetchNext() {
if (!queue.isEmpty()) {
final QueueItem[] copy = queue.toArray(new QueueItem[queue
.size()]);
queue.clear();
for (QueueItem item : copy) {
final Type type = item.type;
final Type superType = type.getSuperType();
if (superType != null) {
final QueueItem superItem = new QueueItem(superType,
item.predicate);
if (canVisit(superItem) && isValid(superType)) {
queue.add(superItem);
}
}
for (Type trait : type.getTraits()) {
final QueueItem traitItem = new QueueItem(trait,
item.predicate);
if (canVisit(traitItem) && isValid(trait)) {
queue.add(traitItem);
}
}
}
current = queue.iterator();
return current.hasNext();
}
return false;
}
}
static final int ALL = 0;
static final int SUPERTYPES = 1;
private class MemberIterator extends CompoundIterator<Member> {
private final TypeIterator typeIterator;
private final Set<Type> entrypoints = new HashSet<Type>();
public MemberIterator(int mode) {
for (QueueItem item : types) {
entrypoints.add(item.type);
}
typeIterator = new TypeIterator(mode);
current = Collections.<Member> emptyList().iterator();
}
private Collection<Member> filter(Collection<Member> members) {
final List<Member> result = new ArrayList<Member>();
for (Member member : members) {
if (isValid(member)) {
result.add(member);
}
}
return result;
}
protected boolean isValid(Member member) {
if (member.isStatic()) {
final Type owner = member.getDeclaringType();
return owner != null
&& (owner.isInheritStaticMembers() || entrypoints
.contains(owner));
} else {
return true;
}
}
@Override
protected boolean fetchNext() {
while (typeIterator.hasNext()) {
final QueueItem item = typeIterator.next();
final EList<Member> members = item.type.getMembers();
final Member[] additionalMembers = item.type
.getAdditionalMembers(null);
if (item.predicate == MemberPredicates.ALWAYS_TRUE) {
if (additionalMembers != null
&& additionalMembers.length != 0) {
final List<Member> concat = new ArrayList<Member>(
members.size() + additionalMembers.length);
concat.addAll(members);
Collections.addAll(concat, additionalMembers);
current = filter(concat).iterator();
} else {
current = filter(members).iterator();
}
} else {
final List<Member> filtered = new ArrayList<Member>(
members.size());
for (Member member : members) {
if (item.predicate.evaluate(member) && isValid(member)) {
filtered.add(member);
}
}
if (additionalMembers != null) {
for (Member member : additionalMembers) {
if (item.predicate.evaluate(member)
&& isValid(member)) {
filtered.add(member);
}
}
}
current = filtered.iterator();
}
if (current.hasNext()) {
return true;
}
}
return false;
}
}
public Iterator<Member> iterator() {
return new MemberIterator(ALL);
}
/**
* Iterates over the members ignoring the duplicates, e.g. members with the
* same name.
*
* <p>
* If there is an abstract and a normal method, both with the same name,
* normal method is returned. If there are two normal methods (or two
* abstract ones), the one that is closer to the given type in the
* inheritance chain is returned. Otherwise it's unspecified which one of
* the duplicated members is returned.
* </p>
*/
private class IgnoreDuplicateMemberIterator extends MemberIterator {
private final Set<Object> processed = new HashSet<Object>();
private final Collection<String> ignored;
private List<Object> abstractMethods = new ArrayList<Object>();
public IgnoreDuplicateMemberIterator(Collection<String> ignoreMembers) {
super(ALL);
this.ignored = ignoreMembers != null ? ignoreMembers : Collections
.<String> emptySet();
}
@Override
protected boolean isValid(Member member) {
if (super.isValid(member) && !ignored.contains(member.getName())) {
final Object key = MethodKey.createKey(member);
if (member instanceof Method && ((Method) member).isAbstract()) {
if (!processed.contains(key)) {
abstractMethods.add(key);
abstractMethods.add(member);
}
return false;
} else {
return processed.add(key);
}
} else {
return false;
}
}
@Override
protected boolean fetchNext() {
final boolean result = super.fetchNext();
if (!result) {
if (!abstractMethods.isEmpty()) {
final List<Member> queue = new ArrayList<Member>();
for (int i = 0; i < abstractMethods.size(); i += 2) {
if (processed.add(abstractMethods.get(i))) {
queue.add((Member) abstractMethods.get(i + 1));
}
}
abstractMethods.clear();
if (!queue.isEmpty()) {
current = queue.iterator();
return true;
}
}
}
return result;
}
}
private static class MemberKey {
final String name;
final boolean isStatic;
public MemberKey(Member member) {
this.name = member.getName();
this.isStatic = member.isStatic();
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MemberKey) {
final MemberKey other = (MemberKey) obj;
return name.equals(other.name) && isStatic == other.isStatic;
}
return false;
}
}
private static class MethodKey {
final String name;
final boolean isStatic;
final String signature;
/**
* @param name
*/
public MethodKey(Method method) {
this.name = method.getName();
this.isStatic = method.isStatic();
StringBuilder sb = new StringBuilder();
for (Parameter parameter : method.getParameters()) {
final JSType paramType = parameter.getType();
if (paramType != null) {
sb.append(paramType.getName());
}
sb.append(',');
}
this.signature = sb.toString();
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MethodKey) {
final MethodKey other = (MethodKey) obj;
return name.equals(other.name) && isStatic == other.isStatic
&& signature.equals(other.signature);
}
return false;
}
protected static Object createKey(Member member) {
if (member instanceof Method && member.getDeclaringType() != null
&& member.getDeclaringType().getKind() == TypeKind.JAVA) {
return new MethodKey((Method) member);
} else {
return new MemberKey(member);
}
}
}
/**
* Iterates over type members skipping overloaded methods
*/
public Iterable<Member> ignoreDuplicates() {
return ignoreDuplicates(null);
}
/**
* Iterates over type members skipping overloaded methods and also skipping
* the specified members
*
* @param ignoreMembers
* member names to skip or <code>null</code> if nothing to skip
*/
public Iterable<Member> ignoreDuplicates(
final Collection<String> ignoreMembers) {
return new Iterable<Member>() {
public Iterator<Member> iterator() {
return new IgnoreDuplicateMemberIterator(ignoreMembers);
}
};
}
/**
* Finds the member with the specified name. Returns the member found or
* <code>null</code> otherwise.
*/
public Member findMember(String memberName) {
for (Member member : this) {
if (memberName.equals(member.getName())) {
return member;
}
}
return null;
}
/**
* Checks if this query contains the specified type.
*/
public boolean contains(Type type) {
for (QueueItem item : types) {
if (type == item.type) {
return true;
}
}
return false;
}
@Override
public String toString() {
return getClass().getSimpleName() + types;
}
/**
* Finds the member with the specified name and type. Returns the member
* found or <code>null</code> otherwise.
*/
@SuppressWarnings("unchecked")
protected <T extends Member> T findMember(String memberName,
Class<T> memberType) {
for (Member member : this) {
if (memberName.equals(member.getName())) {
return (T) member;
}
}
return null;
}
/**
* Finds the method with the specified name. Returns the method found or
* <code>null</code> otherwise.
*/
public Method findMethod(String methodName) {
return findMember(methodName, Method.class);
}
/**
* Finds the super method with the specified name.
*/
public Method findSuperMethod(String methodName) {
for (Iterator<Member> i = new MemberIterator(SUPERTYPES); i.hasNext();) {
final Member member = i.next();
if (member instanceof Method && methodName.equals(member.getName())) {
return (Method) member;
}
}
return null;
}
/**
* Finds all the super methods with the specified name. If no super methods
* with the specified name were found then empty list is returned.
*/
public List<Method> findSuperMethods(String methodName) {
List<Method> result = null;
for (Iterator<Member> i = new MemberIterator(SUPERTYPES); i.hasNext();) {
final Member member = i.next();
if (member instanceof Method && methodName.equals(member.getName())) {
if (result == null) {
result = new ArrayList<Method>();
}
result.add((Method) member);
}
}
return result != null ? result : Collections.<Method> emptyList();
}
}