/**
* Copyright (c) 2011 Stefan Henss.
* 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:
* Stefan Henß - initial API and implementation.
*/
package org.eclipse.recommenders.internal.chain.rcp;
import static org.eclipse.recommenders.internal.chain.rcp.l10n.LogMessages.WARNING_CANNOT_USE_AS_PARENT_OF_COMPLETION_LOCATION;
import static org.eclipse.recommenders.utils.Logs.log;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.internal.codeassist.InternalCompletionContext;
import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMessageSend;
import org.eclipse.jdt.internal.codeassist.complete.CompletionOnQualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.InvocationSite;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.util.ObjectVector;
import org.eclipse.recommenders.completion.rcp.IRecommendersCompletionContext;
import org.eclipse.recommenders.utils.names.ITypeName;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@SuppressWarnings("restriction")
public final class TypeBindingAnalyzer {
private static final Predicate<FieldBinding> NON_STATIC_FIELDS_ONLY_FILTER = new Predicate<FieldBinding>() {
@Override
public boolean apply(final FieldBinding m) {
return m.isStatic();
}
};
private static final Predicate<MethodBinding> RELEVANT_NON_STATIC_METHODS_ONLY_FILTER = new Predicate<MethodBinding>() {
@Override
public boolean apply(final MethodBinding m) {
return m.isStatic() || isVoid(m) || m.isConstructor() || hasPrimitiveReturnType(m);
}
};
private static final Predicate<FieldBinding> STATIC_FIELDS_ONLY_FILTER = new Predicate<FieldBinding>() {
@Override
public boolean apply(final FieldBinding m) {
return !m.isStatic();
}
};
private static final Predicate<MethodBinding> STATIC_NON_VOID_NON_PRIMITIVE_METHODS_ONLY_FILTER = new Predicate<MethodBinding>() {
@Override
public boolean apply(final MethodBinding m) {
return !m.isStatic() || isVoid(m) || m.isConstructor() || hasPrimitiveReturnType(m);
}
};
private TypeBindingAnalyzer() {
}
static boolean isVoid(final MethodBinding m) {
return hasPrimitiveReturnType(m) && m.returnType.constantPoolName()[0] == 'V';
}
static boolean hasPrimitiveReturnType(final MethodBinding m) {
return m.returnType.constantPoolName().length == 1;
}
public static Collection<Binding> findVisibleInstanceFieldsAndRelevantInstanceMethods(final TypeBinding type,
final InvocationSite invocationSite, final Scope scope) {
return findFieldsAndMethods(type, invocationSite, scope, NON_STATIC_FIELDS_ONLY_FILTER,
RELEVANT_NON_STATIC_METHODS_ONLY_FILTER);
}
public static Collection<Binding> findAllPublicStaticFieldsAndNonVoidNonPrimitiveStaticMethods(
final TypeBinding type, final InvocationSite invocationSite, final Scope scope) {
return findFieldsAndMethods(type, invocationSite, scope, STATIC_FIELDS_ONLY_FILTER,
STATIC_NON_VOID_NON_PRIMITIVE_METHODS_ONLY_FILTER);
}
private static Collection<Binding> findFieldsAndMethods(final TypeBinding type, final InvocationSite invocationSite,
final Scope scope, final Predicate<FieldBinding> fieldFilter, final Predicate<MethodBinding> methodFilter) {
final Map<String, Binding> tmp = Maps.newLinkedHashMap();
final TypeBinding receiverType = scope.classScope().referenceContext.binding;
for (final ReferenceBinding cur : findAllSupertypesIncludeingArgument(type)) {
for (final MethodBinding method : cur.methods()) {
if (methodFilter.apply(method) || !method.canBeSeenBy(invocationSite, scope)) {
continue;
}
final String key = createMethodKey(method);
if (!tmp.containsKey(key)) {
tmp.put(key, method);
}
}
for (final FieldBinding field : cur.fields()) {
if (fieldFilter.apply(field) || !field.canBeSeenBy(receiverType, invocationSite, scope)) {
continue;
}
final String key = createFieldKey(field);
if (!tmp.containsKey(key)) {
tmp.put(key, field);
}
}
}
return tmp.values();
}
private static List<ReferenceBinding> findAllSupertypesIncludeingArgument(final TypeBinding type) {
final TypeBinding base = removeArrayWrapper(type);
if (!(base instanceof ReferenceBinding)) {
return Collections.emptyList();
}
final List<ReferenceBinding> supertypes = Lists.newLinkedList();
final LinkedList<ReferenceBinding> queue = Lists.newLinkedList();
queue.add((ReferenceBinding) base);
while (!queue.isEmpty()) {
final ReferenceBinding superType = queue.poll();
if (superType == null || supertypes.contains(superType)) {
continue;
}
supertypes.add(superType);
queue.add(superType.superclass());
for (final ReferenceBinding interfc : superType.superInterfaces()) {
queue.add(interfc);
}
}
return supertypes;
}
private static String createFieldKey(final FieldBinding field) {
return new StringBuilder().append(field.name).append(field.type.signature()).toString();
}
private static String createMethodKey(final MethodBinding method) {
final String signature = String.valueOf(method.signature());
final String signatureWithoutReturnType = StringUtils.substringBeforeLast(signature, ")"); //$NON-NLS-1$
return new StringBuilder().append(method.readableName()).append(signatureWithoutReturnType).toString();
}
public static boolean isAssignable(final ChainElement edge, final TypeBinding expectedType,
final int expectedDimension) {
if (expectedDimension <= edge.getReturnTypeDimension()) {
final TypeBinding base = removeArrayWrapper(edge.getReturnType());
if (base instanceof BaseTypeBinding) {
return false;
}
if (base.isCompatibleWith(expectedType)) {
return true;
}
final LinkedList<ReferenceBinding> supertypes = Lists.newLinkedList();
supertypes.add((ReferenceBinding) base);
final String expectedSignature = String.valueOf(expectedType.signature());
while (!supertypes.isEmpty()) {
final ReferenceBinding type = supertypes.poll();
if (String.valueOf(type.signature()).equals(expectedSignature)) {
return true;
}
final ReferenceBinding superclass = type.superclass();
if (superclass != null) {
supertypes.add(superclass);
}
for (final ReferenceBinding intf : type.superInterfaces()) {
supertypes.add(intf);
}
}
}
return false;
}
public static TypeBinding removeArrayWrapper(final TypeBinding type) {
TypeBinding base = type;
while (base instanceof ArrayBinding) {
base = ((ArrayBinding) base).elementsType();
}
return base;
}
public static List<Optional<TypeBinding>> resolveBindingsForExpectedTypes(final IRecommendersCompletionContext ctx,
final Scope scope) {
final InternalCompletionContext context = (InternalCompletionContext) ctx.getJavaContext().getCoreContext();
final ASTNode parent = context.getCompletionNodeParent();
final List<Optional<TypeBinding>> bindings = Lists.newLinkedList();
if (parent instanceof LocalDeclaration) {
bindings.add(Optional.fromNullable(((LocalDeclaration) parent).type.resolvedType));
} else if (parent instanceof ReturnStatement) {
bindings.add(resolveReturnStatement(context));
} else if (parent instanceof FieldDeclaration) {
bindings.add(Optional.fromNullable(((FieldDeclaration) parent).type.resolvedType));
} else if (parent instanceof Assignment) {
bindings.add(Optional.fromNullable(((Assignment) parent).resolvedType));
} else if (isCompletionOnMethodParameter(context)) {
for (final ITypeName type : ctx.getExpectedTypeNames()) {
bindings.add(Optional.of(scope.getType(type.getClassName().toCharArray())));
}
} else {
log(WARNING_CANNOT_USE_AS_PARENT_OF_COMPLETION_LOCATION, parent.getClass());
}
return bindings;
}
private static boolean isCompletionOnMethodParameter(final InternalCompletionContext context) {
return context.getCompletionNode() instanceof CompletionOnQualifiedAllocationExpression
|| context.getCompletionNode() instanceof CompletionOnMessageSend
|| context.getCompletionNodeParent() instanceof MessageSend;
}
private static Optional<TypeBinding> resolveReturnStatement(final InternalCompletionContext context) {
final String expected = String.valueOf(context.getExpectedTypesKeys()[0]);
final ObjectVector methods = context.getVisibleMethods();
for (int i = 0; i < methods.size; ++i) {
final TypeBinding type = ((MethodBinding) methods.elementAt(i)).returnType;
final String key = String.valueOf(type.computeUniqueKey());
if (key.equals(expected)) {
return Optional.of(type);
}
}
return Optional.absent();
}
}