/** * Copyright (C) 2005 - 2012 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.jdt.util; import java.util.ArrayList; import java.util.List; import org.eclim.logging.Logger; import org.eclim.plugin.jdt.command.search.SearchRequestor; import org.eclim.util.StringUtils; import org.eclim.util.file.Position; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageDeclaration; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.ISourceReference; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeParameter; 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.SearchParticipant; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.internal.core.CompilationUnit; /** * Utility methods for working with IType. * * @author Eric Van Dewoestine */ public class TypeUtils { private static final Logger logger = Logger.getLogger(TypeUtils.class); /** * Gets the type at the supplied offset, which will either be the primary type * of the comilation unit, or an inner class. * * @param src The ICompilationSource. * @param offset The offet in the source file. * @return The IType. */ public static IType getType(ICompilationUnit src, int offset) throws Exception { IJavaElement element = src.getElementAt(offset); IType type = null; // offset outside the class source (Above the package declaration most // likely) if(element == null){ type = ((CompilationUnit)src).getTypeRoot().findPrimaryType(); // inner class }else if(element != null && element.getElementType() == IJavaElement.TYPE){ type = (IType)element; }else{ element = element.getParent(); // offset on import statement if(element.getElementType() == IJavaElement.IMPORT_CONTAINER){ element = element.getParent(); } // offset on the package declaration or continuation of import ^ if(element.getElementType() == IJavaElement.COMPILATION_UNIT){ element = ((CompilationUnit)element).getTypeRoot().findPrimaryType(); } type = (IType)element; } return type; } /** * Gets the Position of the suplied ISourceReference. * * @param type The type. * @param reference The reference. * @return The position. */ public static Position getPosition(IType type, ISourceReference reference) throws Exception { ISourceRange range = reference.getSourceRange(); return Position.fromOffset( type.getResource().getLocation().toOSString(), null, range.getOffset(), range.getLength()); } /** * Gets the signature for the supplied type. * * @param typeInfo The typeInfo. * @return The signature. */ public static String getTypeSignature(TypeInfo typeInfo) throws Exception { StringBuffer buffer = new StringBuffer(); IType type = typeInfo.getType(); int flags = type.getFlags(); if(Flags.isPublic(flags)){ buffer.append("public "); } buffer.append(type.isClass() ? "class " : "interface "); IJavaElement parent = type.getParent(); if(parent.getElementType() == IJavaElement.TYPE){ buffer.append(type.getParent().getElementName()).append('.'); }else if(parent.getElementType() == IJavaElement.CLASS_FILE){ int index = parent.getElementName().indexOf('$'); if(index != -1){ buffer.append(parent.getElementName().substring(0, index)).append('.'); } } buffer.append(type.getElementName()); String[] params = typeInfo.getTypeParameters(); String[] args = typeInfo.getTypeArguments(); if (params != null && params.length > 0 && args != null && args.length > 0){ buffer.append('<'); for (int ii = 0; ii < args.length; ii++){ if (ii > 0){ buffer.append(','); } buffer.append(args[ii]); } buffer.append('>'); } return buffer.toString(); } /** * Determines if any of the super types of the supplied type contains the * supplied method and if so return that super type. * * @param type The type. * @param method The method. * @return The super type that contains the method, or null if none. */ public static TypeInfo getSuperTypeContainingMethod(IType type, IMethod method) throws Exception { TypeInfo[] types = getSuperTypes(type); for(TypeInfo info : types){ IMethod[] methods = info.getType().getMethods(); for (IMethod m : methods){ if(m.isSimilar(method)){ return info; } } } return null; } /** * Determines if any of the super types of the supplied type contains a method * with the supplied signature and if so return that super type. * * @param type The type. * @param signature The method signature. * @return The super type that contains the method, or null if none. */ public static Object[] getSuperTypeContainingMethod( IType type, String signature) throws Exception { TypeInfo[] types = getSuperTypes(type); for(TypeInfo info : types){ IMethod[] methods = info.getType().getMethods(); for (IMethod method : methods){ String sig = MethodUtils.getMinimalMethodSignature(method, info); if(sig.equals(signature)){ return new Object[]{info, method}; } } } return null; } /** * Converts the supplied type signature with generic information to the most * basic type that it supports. * * @param type The parent IType. * @param typeSignature The type signature. * @return The base type. */ public static String getBaseTypeFromGeneric( IType type, String typeSignature) throws Exception { int arrayCount = Signature.getArrayCount(typeSignature); if(arrayCount > 0){ for(int ii = 0; ii < arrayCount; ii++){ typeSignature = Signature.getElementType(typeSignature); } } String result = null; ITypeParameter param = type.getTypeParameter( Signature.getSignatureSimpleName(typeSignature)); if(param.exists()){ result = param.getBounds()[0]; }else{ result = Signature.getSignatureSimpleName( Signature.getTypeErasure(typeSignature)); } if(arrayCount > 0){ for(int ii = 0; ii < arrayCount; ii++){ result = result + "[]"; } } return result; } /** * Attempts to find an unqualified type by searching the import declarations * of the supplied source file and attempting to find the type in the same * package as the source file.. * * @param src The source file. * @param typeName The unqualified type name. * @return The type or null if not found. */ public static IType findUnqualifiedType( ICompilationUnit src, String typeName) throws Exception { String name = "." + typeName; // strip of generic type if present. int index = name.indexOf('<'); if(index != -1){ name = name.substring(0, index); } // search imports IImportDeclaration[] imports = src.getImports(); for(IImportDeclaration decl : imports){ if(decl.getElementName().endsWith(name)){ return src.getJavaProject().findType(decl.getElementName()); } } // attempt to find in current package. IPackageDeclaration[] packages = src.getPackageDeclarations(); if(packages != null && packages.length > 0){ name = packages[0].getElementName() + name; }else{ name = typeName; } IType type = src.getJavaProject().findType(name); // last effort, search java.lang if(type == null){ type = src.getJavaProject().findType("java.lang." + typeName); } return type; } /** * Recursively gets all superclass and implemented interfaces from the * supplied type. * * @param type The type. * @return Array of types. */ public static TypeInfo[] getSuperTypes(IType type) throws Exception { return getSuperTypes(type, false); } /** * Recursively gets all superclass and implemented interfaces from the * supplied type. * * @param type The type. * @param returnNotFound Whether or not to return handle only instances to * super types that could not be found in the project. * @return Array of types. */ public static TypeInfo[] getSuperTypes(IType type, boolean returnNotFound) throws Exception { TypeInfo[] interfaces = getInterfaces(type, returnNotFound); TypeInfo[] superClasses = getSuperClasses(type, returnNotFound); TypeInfo[] types = new TypeInfo[interfaces.length + superClasses.length]; System.arraycopy(interfaces, 0, types, 0, interfaces.length); System.arraycopy( superClasses, 0, types, interfaces.length, superClasses.length); return types; } /** * Recursively gets all the superclasses for the given type. * * @param type The type. * @return Array of superclass types. */ public static TypeInfo[] getSuperClasses(IType type) throws Exception { return getSuperClasses(type, false); } /** * Recursively gets all the superclasses for the given type. * * @param type The type. * @param returnNotFound Whether or not to return handle only instances to * super class types that could not be found in the project. * @return Array of superclass types. */ public static TypeInfo[] getSuperClasses(IType type, boolean returnNotFound) throws Exception { ArrayList<TypeInfo> types = new ArrayList<TypeInfo>(); getSuperClasses(type, types, returnNotFound, null); // add java.lang.Object if not already added IType objectType = type.getJavaProject().findType("java.lang.Object"); TypeInfo objectTypeInfo = new TypeInfo(objectType, null, null); if(!types.contains(objectTypeInfo)){ types.add(objectTypeInfo); } return (TypeInfo[])types.toArray(new TypeInfo[types.size()]); } /** * Recursively gets all the implemented interfaces for the given type. * * @param type The type. * @return Array of interface types. */ public static TypeInfo[] getInterfaces(IType type) throws Exception { return getInterfaces(type, false); } /** * Recursively gets all the implemented interfaces for the given type. * * @param type The type. * @param returnNotFound Whether or not to return handle only instances to * super interface types that could not be found in the project. * @return Array of interface types. */ public static TypeInfo[] getInterfaces(IType type, boolean returnNotFound) throws Exception { ArrayList<TypeInfo> types = new ArrayList<TypeInfo>(); getInterfaces(type, types, returnNotFound, null); return (TypeInfo[])types.toArray(new TypeInfo[types.size()]); } /** * Recursively gets all the superclasses for the given type. * * @param type The type. * @param superclasses The list to add results to. * @param includeNotFound Whether or not to include types that were not found * (adds them as handle only IType instances). * @param baseType The first type in the recursion stack. */ public static void getSuperClasses( IType type, List<TypeInfo> superclasses, boolean includeNotFound, TypeInfo baseType) throws Exception { TypeInfo superclassInfo = getSuperClass(type, baseType); if (superclassInfo != null){ if (baseType == null || baseType.getTypeArguments().length != superclassInfo.getTypeArguments().length) { baseType = superclassInfo; } } if(superclassInfo != null && !superclasses.contains(superclassInfo)){ superclasses.add(superclassInfo); getSuperClasses( superclassInfo.getType(), superclasses, includeNotFound, baseType); }else if(superclassInfo == null && includeNotFound){ String typeName = type.getSuperclassName(); if(typeName != null){ // get a handle only reference to the super class that wasn't found. try{ IType superclass = type.getType(typeName); superclassInfo = new TypeInfo(superclass, null, null); if(!superclasses.contains(superclassInfo)){ superclasses.add(superclassInfo); } }catch(Exception e){ // don't let the error cause the command to fail. logger.warn("Unable to get a handle to class not found: '" + typeName + "'", e); } } } } /** * Gets the super type of the supplied type, if any. * * @param type The type to get the superclass of. * @return The superclass type or null if none. */ public static TypeInfo getSuperClass(IType type) throws Exception { return getSuperClass(type, null); } /** * Gets the super type of the supplied type, if any. * * @param type The type to get the superclass of. * @return The superclass type or null if none. */ private static TypeInfo getSuperClass(IType type, TypeInfo baseType) throws Exception { String superclassSig = type.getSuperclassTypeSignature(); if(superclassSig != null){ String qualifier = Signature.getSignatureQualifier(superclassSig); qualifier = (qualifier != null && !qualifier.equals(StringUtils.EMPTY)) ? qualifier + '.' : StringUtils.EMPTY; String superclass = qualifier + Signature.getSignatureSimpleName(superclassSig); String[] args = Signature.getTypeArguments(superclassSig); String[] typeArgs = new String[args.length]; for (int ii = 0; ii < args.length; ii++){ typeArgs[ii] = Signature.getSignatureSimpleName(args[ii]); } if (baseType != null && baseType.getTypeArguments().length == typeArgs.length) { typeArgs = baseType.getTypeArguments(); } String[][] types = type.resolveType(superclass); if(types != null){ for(String[] typeInfo : types){ String typeName = typeInfo[0] + "." + typeInfo[1]; IType found = type.getJavaProject().findType(typeName); if (found != null){ ITypeParameter[] params = found.getTypeParameters(); String[] typeParams = new String[params.length]; for (int ii = 0; ii < params.length; ii++){ typeParams[ii] = params[ii].getElementName(); } return new TypeInfo(found, typeParams, typeArgs); } } }else{ IType found = type.getJavaProject().findType(superclass); if (found != null){ return new TypeInfo(found, null, typeArgs); } } } return null; } /** * Gets an array of directly implemented interfaces for the supplied type, if * any. * * @param type The type to get the interfaces of. * @return Array of interface types. */ public static IType[] getSuperInterfaces(IType type) throws Exception { String[] parents = type.getSuperInterfaceNames(); ArrayList<IType> interfaces = new ArrayList<IType>(parents.length); for(String parent : parents){ String[][] types = type.resolveType(parent); if(types != null){ for(String[] typeInfo : types){ String typeName = typeInfo[0] + "." + typeInfo[1]; IType found = type.getJavaProject().findType(typeName); if(found != null){ interfaces.add(found); } } }else{ IType found = type.getJavaProject().findType(parent); if(found != null){ interfaces.add(found); } } } return interfaces.toArray(new IType[interfaces.size()]); } /** * Recursively gets the interfaces implemented by the given type. * * @param type The type. * @param interfaces The list to add results to. * @param includeNotFound Whether or not to include types that were not found * (adds them as handle only IType instances). * @param baseType The first type in the recursion stack. */ public static void getInterfaces( IType type, List<TypeInfo> interfaces, boolean includeNotFound, TypeInfo baseType) throws Exception { // directly implemented interfaces. String[] parentSigs = type.getSuperInterfaceTypeSignatures(); for(String parentSig : parentSigs){ String parent = Signature.getSignatureSimpleName(parentSig); String[] args = Signature.getTypeArguments(parentSig); String[] typeArgs = new String[args.length]; for (int ii = 0; ii < args.length; ii++){ typeArgs[ii] = Signature.getSignatureSimpleName(args[ii]); } if (baseType != null && baseType.getTypeArguments().length == typeArgs.length) { typeArgs = baseType.getTypeArguments(); } IType found = null; String[][] types = type.resolveType(parent); if(types != null){ for(String[] typeInfo : types){ String typeName = typeInfo[0] + "." + typeInfo[1]; found = type.getJavaProject().findType(typeName); } }else{ found = type.getJavaProject().findType(parent); } if(found != null){ ITypeParameter[] params = found.getTypeParameters(); String[] typeParams = new String[params.length]; for (int ii = 0; ii < params.length; ii++){ typeParams[ii] = params[ii].getElementName(); } TypeInfo typeInfo = new TypeInfo(found, typeParams, typeArgs); if (!interfaces.contains(typeInfo)){ interfaces.add(typeInfo); if (baseType == null || baseType.getTypeArguments().length != typeInfo.getTypeArguments().length) { baseType = typeInfo; } getInterfaces(found, interfaces, includeNotFound, baseType); } }else if(found == null && includeNotFound){ String typeName = parent; if(typeName != null){ // get a handle only reference to the super class that wasn't found. try{ found = type.getType(typeName); TypeInfo typeInfo = new TypeInfo(found, null, typeArgs); if(!interfaces.contains(typeInfo)){ interfaces.add(typeInfo); } }catch(Exception e){ // don't let the error cause the command to fail. logger.warn("Unable to get a handle to interface not found: '" + typeName + "'", e); } } }else if(found == null){ logger.warn("Unable to resolve implmented interface '{}' for '{}'", parent, type.getFullyQualifiedName()); } } // indirectly implemented parents TypeInfo superclassInfo = getSuperClass(type); if(superclassInfo != null){ getInterfaces( superclassInfo.getType(), interfaces, includeNotFound, superclassInfo); } } /** * If the supplied type represents a generic param type, then replace it with * the properly concrete type from the supplied type info. * * @param type The possibly generic param type. * @param typeInfo The type info. * @return The concrete type. */ public static String replaceTypeParams(String type, TypeInfo typeInfo) { if(typeInfo != null){ String[] params = typeInfo.getTypeParameters(); String[] args = typeInfo.getTypeArguments(); if (params != null && params.length == args.length){ for (int ii = 0; ii < params.length; ii++){ type = type.replaceAll("\\b" + params[ii] + "\\b", args[ii]); } } } return type; } /** * Find types by the supplied fully qualified name or unqualified class name. * * @param javaProject The java project to be searched. * @param name The name to search. * * @return A possibly empty array of IType results found. */ public static IType[] findTypes(IJavaProject javaProject, String name) throws Exception { SearchPattern pattern = SearchPattern.createPattern(name, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE); IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[]{javaProject}); SearchRequestor requestor = new SearchRequestor(); SearchEngine engine = new SearchEngine(); SearchParticipant[] participants = new SearchParticipant[]{SearchEngine.getDefaultSearchParticipant()}; engine.search(pattern, participants, scope, requestor, null); ArrayList<IType> types = new ArrayList<IType>(); if (requestor.getMatches().size() > 0){ for (SearchMatch match : requestor.getMatches()){ if(match.getAccuracy() != SearchMatch.A_ACCURATE){ continue; } IJavaElement element = (IJavaElement)match.getElement(); if (element.getElementType() == IJavaElement.TYPE){ types.add((IType)element); } } } return types.toArray(new IType[types.size()]); } }