blob: 2faa0a440fa055fb40492474df67eac077ace559 [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.javascript.typeinfo.model.TypeKind;
import org.eclipse.dltk.utils.CompoundIterator;
/**
* 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 RTypeMemberQuery implements Iterable<IRMember> {
private static class QueueItem {
final IRTypeDeclaration type;
final MemberPredicate predicate;
public QueueItem(IRTypeDeclaration 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();
}
}
final List<QueueItem> types = new ArrayList<QueueItem>();
/**
* Creates empty query
*/
public RTypeMemberQuery() {
}
/**
* Creates query for all the members of the specified type.
*/
public RTypeMemberQuery(IRTypeDeclaration type) {
add(type);
}
/**
* Creates query for the members of the specified type matching the
* specified predicate.
*
* @see MemberPredicates
*/
public RTypeMemberQuery(IRTypeDeclaration type, MemberPredicate predicate) {
add(type, predicate);
}
/**
* Adds the specified type to this query
*/
public void add(IRTypeDeclaration type) {
add(type, MemberPredicates.ALWAYS_TRUE);
}
/**
* Adds the specified type with the specified predicate to this query.
*/
public void add(IRTypeDeclaration type, MemberPredicate predicate) {
Assert.isNotNull(type);
types.add(new QueueItem(type, predicate));
}
protected boolean isValid(IRTypeDeclaration 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 IRTypeDeclaration type = qi.type;
final IRTypeDeclaration superType = type.getSuperType();
if (superType != null) {
queue.add(new QueueItem(superType, qi.predicate));
}
for (IRTypeDeclaration 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 IRTypeDeclaration type = item.type;
final IRTypeDeclaration superType = type.getSuperType();
if (superType != null) {
final QueueItem superItem = new QueueItem(superType,
item.predicate);
if (canVisit(superItem) && isValid(superType)) {
queue.add(superItem);
}
}
for (IRTypeDeclaration 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<IRMember> {
private final TypeIterator typeIterator;
private final Set<IRTypeDeclaration> entrypoints = new HashSet<IRTypeDeclaration>();
public MemberIterator(int mode) {
for (QueueItem item : types) {
entrypoints.add(item.type);
}
typeIterator = new TypeIterator(mode);
current = Collections.<IRMember> emptyList().iterator();
}
private Collection<IRMember> filter(Collection<IRMember> members) {
final List<IRMember> result = new ArrayList<IRMember>();
for (IRMember member : members) {
if (isValid(member)) {
result.add(member);
}
}
return result;
}
protected boolean isValid(IRMember member) {
if (member.isStatic()) {
final IRTypeDeclaration 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 List<IRMember> members = item.type.getMembers();
if (item.predicate == MemberPredicates.ALWAYS_TRUE) {
current = filter(members).iterator();
} else {
final List<IRMember> filtered = new ArrayList<IRMember>(
members.size());
for (IRMember member : members) {
if (item.predicate.evaluate(member) && isValid(member)) {
filtered.add(member);
}
}
current = filtered.iterator();
}
if (current.hasNext()) {
return true;
}
}
return false;
}
}
public Iterator<IRMember> 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(IRMember member) {
if (super.isValid(member) && !ignored.contains(member.getName())) {
final Object key = MethodKey.createKey(member);
if (member instanceof IRMethod
&& ((IRMethod) 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<IRMember> queue = new ArrayList<IRMember>();
for (int i = 0; i < abstractMethods.size(); i += 2) {
if (processed.add(abstractMethods.get(i))) {
queue.add((IRMember) 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(IRMember 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(IRMethod method) {
this.name = method.getName();
this.isStatic = method.isStatic();
StringBuilder sb = new StringBuilder();
for (IRParameter parameter : method.getParameters()) {
final IRType 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(IRMember member) {
if (member instanceof IRMethod && member.getDeclaringType() != null
&& member.getDeclaringType().getKind() == TypeKind.JAVA) {
return new MethodKey((IRMethod) member);
} else {
return new MemberKey(member);
}
}
}
/**
* Iterates over type members skipping overloaded methods
*/
public Iterable<IRMember> 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<IRMember> ignoreDuplicates(
final Collection<String> ignoreMembers) {
return new Iterable<IRMember>() {
public Iterator<IRMember> iterator() {
return new IgnoreDuplicateMemberIterator(ignoreMembers);
}
};
}
/**
* Finds the member with the specified name. Returns the member found or
* <code>null</code> otherwise.
*/
public IRMember findMember(String memberName) {
for (IRMember member : this) {
if (memberName.equals(member.getName())) {
return member;
}
}
return null;
}
/**
* Finds the member of the specified type with the specified name. Returns
* the member found or <code>null</code> otherwise.
*/
@SuppressWarnings("unchecked")
protected <T extends IRMember> T findMember(String memberName,
Class<T> memberType) {
for (IRMember member : this) {
if (memberType.isInstance(member)
&& memberName.equals(member.getName())) {
return (T) member;
}
}
return null;
}
/**
* Finds the method with the specified name.
*/
public IRMethod findMethod(String methodName) {
return findMember(methodName, IRMethod.class);
}
/**
* Finds the property with the specified name.
*/
public IRProperty findProperty(String propertyName) {
return findMember(propertyName, IRProperty.class);
}
/**
* Checks if this query contains the specified type.
*/
public boolean contains(IRTypeDeclaration type) {
for (QueueItem item : types) {
if (type == item.type) {
return true;
}
}
return false;
}
@Override
public String toString() {
return getClass().getSimpleName() + types;
}
/**
* Finds the super method with the specified name.
*/
public IRMethod findSuperMethod(String methodName) {
for (Iterator<IRMember> i = new MemberIterator(SUPERTYPES); i.hasNext();) {
final IRMember member = i.next();
if (member instanceof IRMethod
&& methodName.equals(member.getName())) {
return (IRMethod) member;
}
}
return null;
}
}