/** * 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; import static com.google.common.base.Optional.*; import static org.eclipse.recommenders.internal.rcp.l10n.LogMessages.*; import static org.eclipse.recommenders.utils.Checks.ensureIsNotNull; import static org.eclipse.recommenders.utils.Logs.log; import static org.eclipse.recommenders.utils.Throws.throwUnhandledException; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.SearchRequestor; import org.eclipse.jdt.internal.codeassist.impl.AssistSourceMethod; import org.eclipse.jdt.internal.codeassist.impl.AssistSourceType; import org.eclipse.jdt.internal.corext.util.SearchUtils; import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache; import org.eclipse.recommenders.rcp.utils.JdtUtils; import org.eclipse.recommenders.utils.Nullable; import org.eclipse.recommenders.utils.names.IMethodName; import org.eclipse.recommenders.utils.names.IName; import org.eclipse.recommenders.utils.names.ITypeName; import org.eclipse.recommenders.utils.names.Names; import org.eclipse.recommenders.utils.names.VmMethodName; import org.eclipse.recommenders.utils.names.VmTypeName; import com.google.common.base.Optional; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; @SuppressWarnings("restriction") public class JavaElementResolver { public static JavaElementResolver INSTANCE; public JavaElementResolver() { INSTANCE = this; } private final BiMap<IName, IJavaElement> cache = HashBiMap.create(); public Set<IMethodName> failedRecMethods = new HashSet<>(); public Set<ITypeName> failedRecTypes = new HashSet<>(); public Optional<IType> toJdtType(final ITypeName recType) { ensureIsNotNull(recType); if (failedRecTypes.contains(recType)) { return absent(); } IType jdtType = (IType) cache.get(recType); if (jdtType == null) { jdtType = resolveType(recType).orNull(); if (jdtType != null) { registerRecJdtElementPair(recType, jdtType); } else { failedRecTypes.add(recType); } } else if (!jdtType.exists()) { // found in cache but not existing anymore? // restart resolution process: cache.remove(recType); return toJdtType(recType); } return fromNullable(jdtType); } public ITypeName toRecType(IType jdtType) { ensureIsNotNull(jdtType); jdtType = JdtUtils.resolveJavaElementProxy(jdtType); ITypeName recType = (ITypeName) cache.inverse().get(jdtType); if (recType == null) { String fullyQualifiedName = jdtType.getFullyQualifiedName(); recType = VmTypeName.get('L' + fullyQualifiedName.replace('.', '/')); registerRecJdtElementPair(recType, jdtType); } return recType; } private Optional<IType> resolveType(final ITypeName recType) { // TODO woah, what a hack just to find a nested/anonymous type... this // definitely needs refactoring! ensureIsNotNull(recType); if (recType.isArrayType()) { // TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=339806 // should throw an exception? or return an Array type? log(ERROR_ARRAY_TYPE_IN_JAVA_ELEMENT_RESOLVER, recType); return absent(); } if (recType.isNestedType()) { final ITypeName declaringType = recType.getDeclaringType(); final String simpleName = StringUtils.substringAfterLast(recType.getIdentifier(), "$"); //$NON-NLS-1$ final IType parent = resolveType(declaringType).orNull(); if (parent != null) { try { for (final IType nested : parent.getTypes()) { final String key = nested.getKey(); if (key.equals(recType.getIdentifier() + ';')) { return fromNullable(nested); } } for (final IMethod m : parent.getMethods()) { for (final IJavaElement children : m.getChildren()) { if (children instanceof IType) { final IType nested = (IType) children; if (nested.getKey().endsWith(simpleName + ';')) { return of(nested); } final String key = nested.getKey(); if (key.equals(recType.getIdentifier() + ';')) { return fromNullable(nested); } } } } } catch (final Exception x) { return absent(); } } return absent(); } final IType[] res = new IType[1]; final IJavaSearchScope scope = SearchEngine.createWorkspaceScope(); final SearchEngine search = new SearchEngine(); final String srcTypeName = Names.vm2srcTypeName(recType.getIdentifier()); final SearchPattern pattern = SearchPattern.createPattern(srcTypeName, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_FULL_MATCH); try { search.search(pattern, SearchUtils.getDefaultSearchParticipants(), scope, new SearchRequestor() { @Override public void acceptSearchMatch(final SearchMatch match) throws CoreException { IType element = (IType) match.getElement(); // with the current settings the engine matches 'Lnull' with 'Ljava/lang/ref/ReferenceQueue$Null' if (toRecType(element).equals(recType)) { res[0] = element; } } }, null); } catch (final CoreException e) { throwUnhandledException(e); } return fromNullable(res[0]); } private void registerRecJdtElementPair(final IName recName, final IJavaElement jdtElement) { ensureIsNotNull(recName); ensureIsNotNull(jdtElement); if (jdtElement instanceof AssistSourceType) { return; } else if (jdtElement instanceof AssistSourceMethod) { return; } cache.forcePut(recName, jdtElement); // XXX checkIsNull(put); } public Optional<IMethod> toJdtMethod(final IMethodName recMethod) { ensureIsNotNull(recMethod); // failedRecMethods.clear() if (failedRecMethods.contains(recMethod)) { return absent(); } IMethod jdtMethod = (IMethod) cache.get(recMethod); if (jdtMethod != null && !jdtMethod.exists()) { jdtMethod = null; } if (jdtMethod == null) { jdtMethod = resolveMethod(recMethod).orNull(); if (jdtMethod == null) { // if (!recMethod.isSynthetic()) { // System.err.printf("resolving %s failed. Is it an compiler generated constructor?\n.", // recMethod.getIdentifier()); // } failedRecMethods.add(recMethod); return absent(); } registerRecJdtElementPair(recMethod, jdtMethod); } else if (!jdtMethod.exists()) { // found in cache but not existing anymore? // restart resolution process: cache.remove(recMethod); return toJdtMethod(recMethod); } return fromNullable(jdtMethod); } /** * Returns null if we fail to resolve all types used in the method signature, for instance generic return types * etc... * */ // This method should return IMethodNames in all cases but yet it does not work completely as we want it to work public Optional<IMethodName> toRecMethod(@Nullable IMethod jdtMethod) { if (jdtMethod == null) { return absent(); } if (!jdtMethod.exists()) { // compiler generated methods (e.g., calls to constructors to inner non-static classes do not exist. return absent(); } JdtUtils.resolveJavaElementProxy(jdtMethod); IMethodName recMethod = (IMethodName) cache.inverse().get(jdtMethod); if (recMethod == null) { try { final IType jdtDeclaringType = jdtMethod.getDeclaringType(); ITypeParameter[] typeParameters = jdtMethod.getTypeParameters(); // final String[] unresolvedParameterTypes = jdtMethod.getParameterTypes(); final String[] resolvedParameterTypes = new String[unresolvedParameterTypes.length]; for (int i = resolvedParameterTypes.length; i-- > 0;) { resolvedParameterTypes[i] = resolveType(jdtDeclaringType, unresolvedParameterTypes[i], typeParameters); } String resolvedReturnType = resolveType(jdtDeclaringType, jdtMethod.getReturnType(), typeParameters); final String methodSignature = Names.src2vmMethod( jdtMethod.isConstructor() ? "<init>" : jdtMethod.getElementName(), resolvedParameterTypes, //$NON-NLS-1$ resolvedReturnType); final ITypeName recDeclaringType = toRecType(jdtDeclaringType); recMethod = VmMethodName.get(recDeclaringType.getIdentifier(), methodSignature); registerRecJdtElementPair(recMethod, jdtMethod); } catch (final Exception e) { log(ERROR_FAILED_TO_RESOLVE_METHOD, jdtMethod, e.getMessage(), e); return absent(); } } return fromNullable(recMethod); } private String resolveType(final IType jdtDeclaringType, final String unresolvedType, final ITypeParameter[] typeParameters) throws JavaModelException { final int arrayCount = Signature.getArrayCount(unresolvedType); String toResolve = unresolvedType.substring(arrayCount); if (typeParameters != null && toResolve.startsWith("Q")) { //$NON-NLS-1$ String typeName = toResolve.substring(1, toResolve.length() - 1); for (ITypeParameter typeParameter : typeParameters) { if (!typeParameter.getElementName().equals(typeName)) { continue; } String[] boundsSignatures = typeParameter.getBoundsSignatures(); if (boundsSignatures.length > 0) { toResolve = boundsSignatures[0]; break; } } } String resolvedType = JdtUtils .resolveUnqualifiedTypeNamesAndStripOffGenericsAndArrayDimension(toResolve, jdtDeclaringType) .or(Signature.SIG_VOID); resolvedType = resolvedType + StringUtils.repeat("[]", arrayCount); //$NON-NLS-1$ return resolvedType; } private Optional<IMethod> resolveMethod(final IMethodName recMethod) { ensureIsNotNull(recMethod); try { final IType jdtType = toJdtType(recMethod.getDeclaringType()).orNull(); if (!isSuccessfullyResolvedType(jdtType)) { return absent(); } List<IType> supertypes = createListOfSupertypes(jdtType); for (final IType t : supertypes) { for (final IMethod m : t.getMethods()) { if (sameSignature(recMethod, m)) { return of(m); } } } return absent(); } catch (final Exception e) { log(ERROR_FAILED_TO_RESOLVE_METHOD, recMethod, e.getMessage(), e); return absent(); } } private List<IType> createListOfSupertypes(final IType jdtType) throws JavaModelException { final ITypeHierarchy hierarchy = SuperTypeHierarchyCache.getTypeHierarchy(jdtType); List<IType> supertypes = Lists.newArrayList(jdtType); for (IType supertype : hierarchy.getAllSupertypes(jdtType)) { supertypes.add(supertype); } if (jdtType.isInterface()) { // ensure java.lang.Object is in the list to resolve, e.g., calls to 'interface.getClass()' for (IType s : hierarchy.getRootClasses()) { supertypes.add(s); } } return supertypes; } private boolean sameSignature(final IMethodName recMethod, final IMethod jdtMethod) throws JavaModelException { if (!(bothConstructors(recMethod, jdtMethod) || sameName(recMethod, jdtMethod))) { return false; } final ITypeName[] recTypes = recMethod.getParameterTypes(); final String[] jdtTypes = jdtMethod.getParameterTypes(); if (!sameNumberOfParameters(recMethod, jdtMethod)) { return false; } for (int i = 0; i < recTypes.length; i++) { final Optional<ITypeName> jdtType = JdtUtils.resolveUnqualifiedJDTType(jdtTypes[i], jdtMethod); // TODO XXX: checking for simple names is not clean! getClassName() if (!jdtType.isPresent()) { return false; } if (!sameSimpleTypes(recTypes[i], jdtType.get()) || !sameArrayDimensions(recTypes[i], jdtTypes[i])) { return false; } } return true; } private boolean sameSimpleTypes(final ITypeName t1, final ITypeName t2) { return t1.getClassName().equals(t2.getClassName()); } private boolean sameArrayDimensions(final ITypeName t1, final String jdtTypes) { int dim1 = t1.getArrayDimensions(); int dim2 = Signature.getArrayCount(jdtTypes.toCharArray()); return dim1 == dim2; } private boolean sameNumberOfParameters(final IMethodName recMethod, final IMethod m) throws JavaModelException { return recMethod.getParameterTypes().length == m.getParameters().length; } private boolean sameName(final IMethodName recMethod, final IMethod m) { return recMethod.getName().equals(m.getElementName()); } private boolean bothConstructors(final IMethodName recMethod, final IMethod m) throws JavaModelException { return recMethod.isInit() && m.isConstructor(); } private boolean isSuccessfullyResolvedType(final IType jdtType) throws JavaModelException { return jdtType != null && jdtType.isStructureKnown(); } }