/******************************************************************************* * 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.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; } }