blob: 04e538a4b1322200583cfdbc04abee4f1e383f2f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 NumberFour AG
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.core.Predicate;
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;
/**
* 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 Predicate<Member> predicate;
public QueueItem(Type type, Predicate<Member> 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();
}
}
private 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 MemberPredicate
*/
public TypeMemberQuery(Type type, Predicate<Member> predicate) {
add(type, predicate);
}
/**
* Adds the specified type to this query
*/
public void add(Type type) {
add(type, MemberPredicate.ALWAYS_TRUE);
}
/**
* Adds the specified type with the specified predicate to this query.
*/
public void add(Type type, Predicate<Member> 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() {
queue.addAll(types);
current = queue.iterator();
}
private boolean canVisit(QueueItem item) {
return visited.add(item);
}
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;
}
}
private class MemberIterator extends CompoundIterator<Member> {
private final TypeIterator typeIterator;
private final Set<Type> entrypoints = new HashSet<Type>();
public MemberIterator() {
for (QueueItem item : types) {
entrypoints.add(item.type);
}
typeIterator = new TypeIterator();
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();
if (item.predicate == MemberPredicate.ALWAYS_TRUE) {
current = filter(item.type.getMembers()).iterator();
} else {
final List<Member> filtered = new ArrayList<Member>(
item.type.getMembers().size());
for (Member member : item.type.getMembers()) {
if (item.predicate.evaluate(member)) {
filtered.add(member);
}
}
current = filter(filtered).iterator();
}
if (current.hasNext()) {
return true;
}
}
return false;
}
}
public Iterator<Member> iterator() {
return new MemberIterator();
}
/**
* 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) {
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;
}
}