blob: 5f7e69b65124ed7c8c5db385cac28871f14af3a1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020, 2021 Axon Ivy.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Reto Weiss/Axon Ivy Cache resolved types
*
********************************************************************************/
package org.eclipse.jst.jsf.common.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.internal.types.TypeConstants;
/**
* Resolves types of a type and caches them
* @author rwei
* @since 27.11.2020
*/
public class JDTTypeResolver
{
private final Map<String, IType> resolvedTypes = new HashMap<String, IType>();
private final IType type;
private final IType[] superTypes;
/**
* Constructor
* @param type
* @param superTypes
*/
public JDTTypeResolver(IType type, IType[] superTypes)
{
this.type = type;
this.superTypes = superTypes;
}
/**
* @param unresolvedSignature
* @return resolved method signatures with erased type parameters
*/
public String resolveMethodEraseTypeParams(String unresolvedSignature)
{
return resolveMethod(unresolvedSignature, TypeParameters.ERASE);
}
/**
* @param unresolvedSignature
* @return resolve method signature with type parameters
*/
public String resolveMethodKeepTypeParams(String unresolvedSignature)
{
return resolveMethod(unresolvedSignature, TypeParameters.KEEP);
}
private String resolveMethod(String unresolvedSignature, TypeParameters eraseTypeParameters)
{
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] = resolve(parameters[i], eraseTypeParameters);
}
// resolve return type
final String resolvedReturn = resolve(Signature.getReturnType(unresolvedSignatureNormalized),
eraseTypeParameters);
return Signature.createMethodSignature(parameters, resolvedReturn);
}
/**
* @param unresolvedSignature
* @return resolved signature with erased type params
*/
public String resolveEraseTypeParams(String unresolvedSignature)
{
return resolve(unresolvedSignature, TypeParameters.ERASE);
}
/**
* @param unresolvedSignature
* @return resolved signature with type params
*/
public String resolveKeepTypeParams(String unresolvedSignature)
{
return resolve(unresolvedSignature, TypeParameters.KEEP);
}
private String resolve(String unresolvedSignature, TypeParameters eraseTypeParameters)
{
final int sigKind = Signature.getTypeSignatureKind(unresolvedSignature);
switch (sigKind)
{
case Signature.BASE_TYPE_SIGNATURE:
return unresolvedSignature;
case Signature.ARRAY_TYPE_SIGNATURE:
{
final String elementType = Signature.getElementType(unresolvedSignature);
if (Signature.getTypeSignatureKind(elementType) == Signature.BASE_TYPE_SIGNATURE)
{
return unresolvedSignature;
}
final String resolvedElementType = resolveRelative(elementType, eraseTypeParameters);
String resultType = ""; //$NON-NLS-1$
for (int i = 0; i < Signature.getArrayCount(unresolvedSignature); i++)
{
resultType += Signature.C_ARRAY;
}
return resultType + resolvedElementType;
}
case Signature.TYPE_VARIABLE_SIGNATURE:
return resolveRelative(unresolvedSignature, eraseTypeParameters);
case Signature.CLASS_TYPE_SIGNATURE:
return resolveRelative(unresolvedSignature, 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 (unresolvedSignature.charAt(0) == Signature.C_STAR)
{
return TypeConstants.TYPE_JAVAOBJECT;
}
return resolve(unresolvedSignature.substring(1), eraseTypeParameters);
case Signature.CAPTURE_TYPE_SIGNATURE:
// strip the capture and try again
return resolve(Signature.removeCapture(unresolvedSignature), eraseTypeParameters);
// case Signature.TYPE_VARIABLE_SIGNATURE:
// resolveSignatureRelative(owningType, typeSignature,
// eraseTypeParameters);
default:
return unresolvedSignature;
}
}
/**
* @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 String resolveRelative(final String typeSignature, final TypeParameters 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 = resolveType(typeSignature);
if (resolvedType != null)
{
if (eraseTypeParameters == TypeParameters.KEEP)
{
// 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 = resolveRelative(
// use the enclosing type,
// *not* the resolved type because
// we need to resolve in that context
typeParam, eraseTypeParameters);
typeParameters.add(resolvedParameter);
}
}
final String resolvedTypeSignature = Signature.createTypeSignature(resolvedType.getFullyQualifiedName(),
true);
if (typeParameters.size() > 0 && eraseTypeParameters == TypeParameters.KEEP)
{
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 = type.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 IType resolveType(final String typeSignature)
{
final String typeName = TypeUtil.getFullyQualifiedName(typeSignature);
return resolveTypeName(typeName);
}
IType resolveTypeName(String typeName)
{
IType resolved = resolvedTypes.get(typeName);
if (resolved != null)
{
return resolved;
}
resolved = resolveTypeNameRelative(typeName);
resolvedTypes.put(typeName, resolved);
return resolved;
}
private IType resolveTypeNameRelative(String typeName)
{
try
{
String[][] resolved = type.resolveType(typeName);
if (resolved != null && resolved.length > 0)
{
return type.getJavaProject().findType(resolved[0][0], resolved[0][1]);
}
return resolveInParents(typeName);
}
catch (JavaModelException jme)
{
return null;
}
}
private IType resolveInParents(String fullyQualifiedName)
throws JavaModelException
{
for (IType superType : superTypes)
{
String[][] resolved = superType.resolveType(fullyQualifiedName);
if (resolved != null && resolved.length > 0)
{
return type.getJavaProject().findType(resolved[0][0], resolved[0][1]);
}
}
return null;
}
private enum TypeParameters
{
ERASE, KEEP
}
}