/******************************************************************************* * Copyright (c) 2006 Oracle Corporation. * 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: * Cameron Bateman/Oracle - initial API and implementation * ********************************************************************************/ package org.eclipse.jst.jsf.common.util; import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jst.jsf.common.JSFCommonPlugin; import org.eclipse.jst.jsf.common.internal.types.TypeConstants; import org.eclipse.jst.jsf.common.internal.types.TypeInfoCache; /** * Utility for handling IType's and type signatures * * Class is static and cannot be extended or instantiated. * * @author cbateman * */ public final class TypeUtil { static IType resolveType(final IType owningType, final String typeSignature) { // if type signature is already resolved then simply look it up if (typeSignature.charAt(0) == Signature.C_RESOLVED || (Signature.getTypeSignatureKind(typeSignature) == Signature.BASE_TYPE_SIGNATURE) || (Signature.getTypeSignatureKind(typeSignature) == Signature.ARRAY_TYPE_SIGNATURE && Signature.getElementType(typeSignature).charAt(0) == Signature.C_RESOLVED)) { IType type = null; try { type = owningType.getJavaProject(). findType(getFullyQualifiedName(typeSignature)); } catch (JavaModelException jme) { // do nothing; return type == null; } return type; } return resolveTypeRelative(owningType, typeSignature); } /** * Fully equivalent to: * * #resolveTypeSignature(owningType, typeSignature, true) * * If resolved, type signature has generic type parameters erased (absent). * * @param owningType * @param typeSignature * @return the resolved type signature for typeSignature in owningType or * typeSignature unchanged if cannot resolve. */ public static String resolveTypeSignature(final IType owningType, final String typeSignature) { return resolveTypeSignature(owningType, typeSignature, true); } /** * Resolve typeSignature in the context of owningType. This method will return * a type erased signture if eraseTypeParameters == true and will attempt to * resolve and include parameters if eraseTypeParamters == false * * NOTE: special rules apply to the way unresolved type parameters and wildcards * are resolved: * * 1) If a fully unresolved type parameter is found, then it will be replaced with Ljava.lang.Object; * * i.e. List<T> -> Ljava.util.List<Ljava.lang.Object;>; for any unresolved T. * * 2) Any bounded wildcard will be replaced by the bound: * * i.e. List<? extends String> -> Ljava.util.List<Ljava.lang.String;>; * i.e. List<? super String> -> Ljava.util.List<Ljava.lang.String;>; * * Note limitation here: bounds that use 'super' will take the "best case" scenario that the list * type is of that type. * * 3) The unbounded wildcard will be replaced by Ljava.lang.Object; * * i.e. List<?> -> Ljava.util.List<Ljava.lang.Object;>; * * * The reason for this substition is to return the most accurate reasonable approximation * of the type within what is known by owningType * * @param owningType * @param typeSignature * @param eraseTypeParameters if set to false, type parameters are resolved included * in the signature * @return the resolved type signature for typeSignature in owningType or * typeSignature unchanged if cannot resolve. */ public static String resolveTypeSignature(final IType owningType, final String typeSignature, boolean eraseTypeParameters) { final int sigKind = Signature.getTypeSignatureKind(typeSignature); switch (sigKind) { case Signature.BASE_TYPE_SIGNATURE: return typeSignature; case Signature.ARRAY_TYPE_SIGNATURE: { final String elementType = Signature.getElementType(typeSignature); if (Signature.getTypeSignatureKind(elementType) == Signature.BASE_TYPE_SIGNATURE) { return typeSignature; } final String resolvedElementType = resolveSignatureRelative(owningType, elementType, eraseTypeParameters); String resultType = ""; //$NON-NLS-1$ for (int i = 0; i < Signature.getArrayCount(typeSignature);i++) { resultType+=Signature.C_ARRAY; } return resultType+resolvedElementType; } case Signature.TYPE_VARIABLE_SIGNATURE: return resolveSignatureRelative(owningType, typeSignature, eraseTypeParameters); case Signature.CLASS_TYPE_SIGNATURE: return resolveSignatureRelative(owningType, typeSignature, eraseTypeParameters); case Signature.WILDCARD_TYPE_SIGNATURE: // strip the wildcard and try again. Too bad Signature doesn't seem to have a method // for this if (typeSignature.charAt(0) == Signature.C_STAR) { return TypeConstants.TYPE_JAVAOBJECT; } return resolveTypeSignature(owningType, typeSignature.substring(1), eraseTypeParameters); case Signature.CAPTURE_TYPE_SIGNATURE: // strip the capture and try again return resolveTypeSignature(owningType, Signature.removeCapture(typeSignature), eraseTypeParameters); // case Signature.TYPE_VARIABLE_SIGNATURE: // resolveSignatureRelative(owningType, typeSignature, eraseTypeParameters); default: return typeSignature; } } /** * @param owningType -- type relative to which typeSignature will be resolved * @param typeSignature -- non-array type signature * @return the resolved type signature if possible or typeSignature if not */ private static String resolveSignatureRelative(final IType owningType, final String typeSignature, final boolean eraseTypeParameters) { // if already fully resolved, return the input if (typeSignature.charAt(0) == Signature.C_RESOLVED) { return typeSignature; } List<String> typeParameters = new ArrayList<String>(); IType resolvedType = resolveTypeRelative(owningType, typeSignature); if (resolvedType != null) { if (!eraseTypeParameters) { // ensure that type parameters are resolved recursively for (String typeParam : Signature.getTypeArguments(typeSignature)) { typeParam = Signature.removeCapture(typeParam); // check and remove bound wildcarding (extends/super/?) if (Signature.getTypeSignatureKind(typeParam) == Signature.WILDCARD_TYPE_SIGNATURE) { // convert ? to Object, strip extends/super if (typeParam.charAt(0) == Signature.C_STAR) { typeParam = TypeConstants.TYPE_JAVAOBJECT; } else { typeParam = typeParam.substring(1); } } final String resolvedParameter = resolveSignatureRelative( // use the enclosing type, // *not* the resolved type because // we need to resolve in that context owningType, typeParam, eraseTypeParameters); typeParameters.add(resolvedParameter); } } final String resolvedTypeSignature = Signature.createTypeSignature (resolvedType.getFullyQualifiedName(), true); if (typeParameters.size() > 0 && !eraseTypeParameters) { StringBuffer sb = new StringBuffer(resolvedTypeSignature); if (sb.charAt(sb.length()-1) == ';') { sb = sb.delete(sb.length()-1, sb.length()); } sb.append("<"); //$NON-NLS-1$ for(String param : typeParameters) { //System.out.println("type param: "+resolvedType.getTypeParameter(param)); sb.append(param); } // replace the dangling ',' with the closing ">" sb.append(">;"); //$NON-NLS-1$ return sb.toString(); } return resolvedTypeSignature; } if (Signature.getTypeSignatureKind(typeSignature) == Signature.CLASS_TYPE_SIGNATURE || Signature.getTypeSignatureKind(typeSignature) == Signature.TYPE_VARIABLE_SIGNATURE) { // if we are unable to resolve, check to see if the owning type has // a parameter by this name ITypeParameter typeParam = owningType.getTypeParameter(Signature.getSignatureSimpleName(typeSignature)); // if we have a type parameter and it hasn't been resolved to a type, // then assume it is a method template placeholder (i.e. T in ArrayList). // at runtime these unresolved parameter variables are effectively // turned into Object's. For example, think List.add(E o). At runtime, // E will behave exactly like java.lang.Object in that signature if (typeParam.exists()) { return TypeConstants.TYPE_JAVAOBJECT; } // TODO: is there a better way to handle a failure to resolve // than just garbage out? //JSFCommonPlugin.log(new Exception("Failed to resolve type: "+typeSignature), "Failed to resolve type: "+typeSignature); //$NON-NLS-1$ //$NON-NLS-2$ } return typeSignature; } private static IType resolveTypeRelative(final IType owningType, final String typeSignature) { final String fullName = getFullyQualifiedName(typeSignature); IType resolvedType = null; try { // TODO: this call is only supported on sourceTypes! String[][] resolved = owningType.resolveType(fullName); if (resolved != null && resolved.length > 0) { resolvedType = owningType.getJavaProject().findType(resolved[0][0], resolved[0][1]); } else { resolvedType = resolveInParents(owningType, fullName); } } catch (JavaModelException jme) { // do nothing; newType == null } return resolvedType; } /** * @param type * @return a type signature for a type */ public static String getSignature(IType type) { final String fullyQualifiedName = type.getFullyQualifiedName(); return Signature.createTypeSignature(fullyQualifiedName, true); } /** * @param owner * @param unresolvedSignature * @return the resolved method signature for unresolvedSignature in owner */ public static String resolveMethodSignature(final IType owner, final String unresolvedSignature) { final String unresolvedSignatureNormalized = unresolvedSignature.replaceAll("/", "."); //$NON-NLS-1$ //$NON-NLS-2$ // get the list of parameters final String[] parameters = Signature.getParameterTypes(unresolvedSignatureNormalized); for (int i = 0; i < parameters.length; i++) { // try to full resolve the type parameters[i] = resolveTypeSignature(owner, parameters[i]); } // resolve return type final String resolvedReturn = resolveTypeSignature(owner, Signature.getReturnType(unresolvedSignatureNormalized)); return Signature.createMethodSignature(parameters, resolvedReturn); } /** * @param typeSignature * @return a fully qualified Java class name from a type signature * i.e. Ljava.lang.String; -> java.lang.String */ public static String getFullyQualifiedName(final String typeSignature) { final String packageName = Signature.getSignatureQualifier(typeSignature); final String typeName = Signature.getSignatureSimpleName(typeSignature); return "".equals(packageName) ? typeName : packageName + "." + typeName; //$NON-NLS-1$//$NON-NLS-2$ } private static IType resolveInParents(IType childType, String fullyQualifiedName) throws JavaModelException { IType resolvedType = null; final TypeInfoCache typeInfoCache = TypeInfoCache.getInstance(); IType[] superTypes = typeInfoCache.getCachedSupertypes(childType); if (superTypes == null) { superTypes = typeInfoCache.cacheSupertypesFor(childType); } String[][] resolved; LOOP_UNTIL_FIRST_MATCH: for (int i = 0; i < superTypes.length; i++) { final IType type = superTypes[i]; resolved = type.resolveType(fullyQualifiedName); if (resolved != null && resolved.length > 0) { resolvedType = childType.getJavaProject().findType(resolved[0][0], resolved[0][1]); break LOOP_UNTIL_FIRST_MATCH; } } return resolvedType; } /** * Attempts to get a Java IType for a fully qualified signature. Note that * generic type arguments are generally ignored by JDT when doing such * look ups. * * @param javaProject the project context inside which to resolve the type * @param fullyResolvedTypeSignature a fully resolved type signature * @return the IType if resolved, null otherwise */ public static IType resolveType(final IJavaProject javaProject, final String fullyResolvedTypeSignature) { String fullyQualifiedName = getFullyQualifiedName(fullyResolvedTypeSignature); fullyQualifiedName = Signature.getTypeErasure(fullyQualifiedName); try { return javaProject.findType(fullyQualifiedName); } catch (JavaModelException e) { // accessible problem JSFCommonPlugin.log(e); return null; } } /** * @param type * @param typeParamSignature -- must be a Type Variable Signature * @param typeArguments * @return the signature for the type argument in typeArguments that matches the * named typeParamSignature in type. * @throws IllegalArgumentException if typeParamSignature is not valid * * For example, given type for java.util.Map, typeParamSignature == "V" and * typeArguments = {Ljava.util.String;, Lcom.test.Blah;}, the result would be * the typeArgument that matches "V", which is "Lcom.test.Blah;} * * returns null if the match cannot be found. */ public static String matchTypeParameterToArgument(final IType type, final String typeParamSignature, final List<String> typeArguments) { if (Signature.getTypeSignatureKind(typeParamSignature) != Signature.TYPE_VARIABLE_SIGNATURE) { throw new IllegalArgumentException(); } try { ITypeParameter[] typeParams = type.getTypeParameters(); for (int pos = 0; pos < typeParams.length; pos++) { if (typeParams[pos].getElementName().equals(Signature.getSignatureSimpleName(typeParamSignature))) { if (pos < typeArguments.size()) { // TODO: should typeArguments.size ever != typeParams.length? return typeArguments.get(pos); } } } } catch (JavaModelException e) { JSFCommonPlugin.log(e); } return null; } /** * @param type * @param fieldName * @return true if fieldName is a member of type. Note that if type is java.lang.Enum * then this will always return true since we cannot know what fields the instance has (it could be any enum) */ public static boolean isEnumMember(final IType type, final String fieldName) { try { if (type == null || !isEnumType(type)) { throw new IllegalArgumentException("type must be non-null and isEnum()==true"); //$NON-NLS-1$ } if (fieldName == null) { throw new IllegalArgumentException("fieldName must be non-null"); //$NON-NLS-1$ } // if type is the java.lang.Enum, always true if (TypeConstants.TYPE_ENUM_BASE.equals(Signature.createTypeSignature(type.getFullyQualifiedName(), true))) { return true; } final IField field = type.getField(fieldName); if (field.exists() && field.isEnumConstant()) { return true; } } catch (JavaModelException jme) { // fall through and return false } return false; } /** * @param typeSig1 the type signature of the first enum. Must be non-null, fully resolved enum type. * @param typeSig2 the type signature of the second enum. Must be non-null, fully resolved enum type. * * @return true if typeSig1.compareTo(typeSig2) is a legal operation (won't throw a CCE) */ public static boolean isEnumsCompareCompatible(final String typeSig1, final String typeSig2) { if (typeSig1 == null || typeSig2 == null) { throw new IllegalArgumentException("args must not be null"); //$NON-NLS-1$ } if (Signature.getTypeSignatureKind(typeSig1) != Signature.CLASS_TYPE_SIGNATURE || Signature.getTypeSignatureKind(typeSig2) != Signature.CLASS_TYPE_SIGNATURE) { throw new IllegalArgumentException("args must be resolved class types"); //$NON-NLS-1$ } // if one or the other is the raw enum type, then they *may* be comparable; we don't know if (TypeConstants.TYPE_ENUM_BASE.equals(typeSig1) || TypeConstants.TYPE_ENUM_BASE.equals(typeSig2)) { return true; } // TODO: support the case of enum base type with generic type argument // only comparable if is the same class return typeSig1.equals(typeSig2); } /** * @param typeSig1 the type signature of the first enum. Must be non-null, fully resolved enum type. * @param typeSig2 the type signature of the second enum. Must be non-null, fully resolved enum type. * @return true if instances typeSig1 and typeSig2 can never be equal due * their being definitively different enum types */ public static boolean canNeverBeEqual(final String typeSig1, final String typeSig2) { if (typeSig1 == null || typeSig2 == null) { throw new IllegalArgumentException("args must not be null"); //$NON-NLS-1$ } if (Signature.getTypeSignatureKind(typeSig1) != Signature.CLASS_TYPE_SIGNATURE || Signature.getTypeSignatureKind(typeSig2) != Signature.CLASS_TYPE_SIGNATURE) { throw new IllegalArgumentException("args must be resolved class types"); //$NON-NLS-1$ } // if either one is the base enum type, then we can't be sure if (TypeConstants.TYPE_ENUM_BASE.equals(typeSig1) || TypeConstants.TYPE_ENUM_BASE.equals(typeSig2)) { return false; } // if they are definitely not the same enum types, then their values // can never be equal return !typeSig1.equals(typeSig2); } /** * NOTE: we diverge from IType.isEnum() because we also return true if the base type * is a java.lang.Enum since we consider this to be "any enumeration type" whereas JDT considers * it merely a class since it doesn't use an "enum" keyword declaration. * @param type * @return true if type is an enum type or is java.lang.Enum */ static boolean isEnumType(IType type) { if (type == null) { return false; } // check if it's the enumeration base type if (TypeConstants.TYPE_ENUM_BASE.equals(Signature.createTypeSignature(type.getFullyQualifiedName(), true))) { return true; } try { return type.isEnum(); } catch (JavaModelException jme) { // log and fallthrough to return false JSFCommonPlugin.log(jme, "Problem resolving isEnum"); //$NON-NLS-1$ } // if unresolved assume false return false; } private TypeUtil() { // no external instantiation } }