/** * Copyright (c) 2010 Darmstadt University of Technology. * 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: * Marcel Bruch - initial API and implementation. */ package org.eclipse.recommenders.rcp.utils; import static com.google.common.base.Optional.*; import static org.apache.commons.lang3.StringUtils.repeat; import static org.eclipse.recommenders.internal.rcp.l10n.LogMessages.ERROR_FAILED_TO_RESOLVE_METHOD; import static org.eclipse.recommenders.utils.Checks.cast; import static org.eclipse.recommenders.utils.Logs.log; import static org.eclipse.recommenders.utils.Throws.*; import java.util.List; import java.util.concurrent.Callable; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.ArrayType; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.QualifiedType; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.UnionType; import org.eclipse.jdt.core.dom.WildcardType; import org.eclipse.recommenders.jdt.AstBindings; import org.eclipse.recommenders.utils.names.IMethodName; import org.eclipse.recommenders.utils.names.ITypeName; import org.eclipse.recommenders.utils.names.Names; import com.google.common.base.Optional; public final class ASTNodeUtils { private ASTNodeUtils() { // Not meant to be instantiated } /** * Returns the names top-level identifier, i.e., for "java.lang.String" --> "String" and "String" --> "String" * * @param name * @return */ public static SimpleName stripQualifier(Name name) { switch (name.getNodeType()) { case ASTNode.SIMPLE_NAME: return (SimpleName) name; case ASTNode.QUALIFIED_NAME: return ((QualifiedName) name).getName(); default: throw throwUnreachable("Unknown subtype of name: '%s'", name.getClass()); //$NON-NLS-1$ } } public static boolean sameSimpleName(final Type jdtParam, final ITypeName crParam) { String jdtTypeName = toSimpleName(jdtParam); String crSimpleName = toSimpleName(crParam); return jdtTypeName.equals(crSimpleName); } private static String toSimpleName(final ITypeName crParam) { String crSimpleName = Names.vm2srcSimpleTypeName(crParam); if (crSimpleName.contains("$")) { //$NON-NLS-1$ crSimpleName = StringUtils.substringAfterLast(crSimpleName, "$"); //$NON-NLS-1$ } return crSimpleName; } /** * Returns the simple name of a given class type, for primitive types their source names "int", "long" etc., for * arrays it returns the element type of the array, for wildcards their bound type, and for union types something * not meaningful. */ private static String toSimpleName(Type type) { SimpleName name; switch (type.getNodeType()) { case ASTNode.SIMPLE_TYPE: { SimpleType t = cast(type); name = stripQualifier(t.getName()); break; } case ASTNode.QUALIFIED_TYPE: { QualifiedType t = cast(type); name = stripQualifier(t.getName()); break; } case ASTNode.PARAMETERIZED_TYPE: { ParameterizedType t = cast(type); return toSimpleName(t.getType()); } case ASTNode.PRIMITIVE_TYPE: { PrimitiveType t = cast(type); return t.getPrimitiveTypeCode().toString(); } case ASTNode.WILDCARD_TYPE: { WildcardType t = cast(type); return toSimpleName(t.getBound()); } case ASTNode.UNION_TYPE: { // TODO: that will probably not work with any name matching... UnionType t = cast(type); return "UnionType" + t.types().toString(); //$NON-NLS-1$ } case ASTNode.ARRAY_TYPE: { ArrayType t = cast(type); return toSimpleName(t.getElementType()) + repeat("[]", t.getDimensions()); //$NON-NLS-1$ } default: throw throwUnreachable("No support for type '%s'", type); //$NON-NLS-1$ } return name.getIdentifier(); } public static int getLineNumberOfNodeStart(final CompilationUnit cuNode, final ASTNode node) { final int startPosition = node.getStartPosition(); return cuNode.getLineNumber(startPosition); } public static int getLineNumberOfNodeEnd(final CompilationUnit cuNode, final ASTNode node) { final int endPosition = node.getStartPosition() + node.getLength(); return cuNode.getLineNumber(endPosition); } public static boolean haveSameNumberOfParameters(final List<SingleVariableDeclaration> jdtParams, final ITypeName[] crParams) { return crParams.length == jdtParams.size(); } public static boolean sameSimpleName(final MethodDeclaration decl, final IMethodName crMethod) { final String methodName = decl.getName().toString(); if (crMethod.isInit()) { final ITypeName declaringType = crMethod.getDeclaringType(); final String className = declaringType.getClassName(); final boolean sameMethodName = className.equals(methodName); return sameMethodName; } return methodName.equals(crMethod.getName()); } public static boolean sameSimpleName(final MethodInvocation invoke, final IMethodName crMethod) { final String methodName = invoke.getName().toString(); if (crMethod.isInit()) { final ITypeName declaringType = crMethod.getDeclaringType(); final String className = declaringType.getClassName(); final boolean sameMethodName = className.equals(methodName); return sameMethodName; } return methodName.equals(crMethod.getName()); } public static boolean sameTypes(final List<Type> jdtTypes, final ITypeName[] crTypes) { for (int i = crTypes.length; --i > 0;) { final Type jdtType = jdtTypes.get(i); final ITypeName crType = crTypes[i]; if (!sameType(jdtType, crType)) { return false; } } return true; } public static boolean sameType(final Type jdtType, final ITypeName crType) { if (jdtType == null || crType == null) { return false; } // if (jdtType.isArrayType() || jdtType.isPrimitiveType()) { return true; } if (jdtType.isSimpleType() && !sameSimpleName(jdtType, crType)) { return false; } final ITypeBinding jdtTypeBinding = jdtType.resolveBinding(); return sameType(jdtTypeBinding, crType); } public static boolean sameType(final ITypeBinding jdtType, final ITypeName crType) { final Optional<ITypeName> opt = AstBindings.toTypeName(jdtType); if (opt.isPresent()) { return opt.get().equals(crType); } return false; } /** * Returns the closes parent ASTnode of the given node-class. Returns the input node if the node already is of the * requested type. */ @SuppressWarnings("unchecked") public static <T extends ASTNode> Optional<T> getClosestParent(ASTNode node, final Class<T> nodeClass) { while (node != null) { if (nodeClass.isInstance(node)) { return (Optional<T>) of(node); } node = node.getParent(); } return absent(); } public static Optional<MethodDeclaration> find(final CompilationUnit cu, final IMethod method) { try { final ISourceRange nameRange = method.getNameRange(); if (nameRange == null || nameRange.getOffset() == -1) { return useVisitor(cu, method); } final ASTNode node = NodeFinder.perform(cu, nameRange); return getClosestParent(node, MethodDeclaration.class); } catch (final JavaModelException e) { log(ERROR_FAILED_TO_RESOLVE_METHOD, method, cu, e); return absent(); } } private static Optional<MethodDeclaration> useVisitor(final CompilationUnit cu, final IMethod member) { return new Finder<Optional<MethodDeclaration>>() { private MethodDeclaration res; @Override public Optional<MethodDeclaration> call() { try { cu.accept(this); } catch (final Exception e) { } return Optional.of(res); } @Override public boolean visit(final MethodDeclaration node) { final IMethodBinding b = node.resolveBinding(); if (member.equals(b.getJavaElement())) { res = node; throwCancelationException(); } return true; } }.call(); } private abstract static class Finder<T> extends ASTVisitor implements Callable<T> { } }