/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.internal.ext.org.objectweb.asm.Opcodes;
import gw.lang.Deprecated;
import gw.lang.GosuShop;
import gw.lang.PublishedName;
import gw.lang.javadoc.IClassDocNode;
import gw.lang.javadoc.IDocRef;
import gw.lang.javadoc.IExceptionNode;
import gw.lang.javadoc.IMethodNode;
import gw.lang.javadoc.IParamNode;
import gw.lang.parser.GosuParserTypes;
import gw.lang.parser.TypeVarToTypeMap;
import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IExceptionInfo;
import gw.lang.reflect.IFeatureInfo;
import gw.lang.reflect.IMethodCallHandler;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IScriptabilityModifier;
import gw.lang.reflect.IType;
import gw.lang.reflect.SimpleParameterInfo;
import gw.lang.reflect.TypeInfoUtil;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGenericTypeVariable;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.java.ClassInfoUtil;
import gw.lang.reflect.java.IJavaAnnotatedElement;
import gw.lang.reflect.java.IJavaClassGenericArrayType;
import gw.lang.reflect.java.IJavaClassInfo;
import gw.lang.reflect.java.IJavaClassMethod;
import gw.lang.reflect.java.IJavaClassParameterizedType;
import gw.lang.reflect.java.IJavaClassType;
import gw.lang.reflect.java.IJavaClassTypeVariable;
import gw.lang.reflect.java.IJavaClassWildcardType;
import gw.lang.reflect.java.IJavaMethodDescriptor;
import gw.lang.reflect.java.IJavaMethodInfo;
import gw.lang.reflect.java.JavaExceptionInfo;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
/**
*/
public class JavaMethodInfo extends JavaBaseFeatureInfo implements IJavaMethodInfo
{
private static final int UNINITED = 0;
private static final int TRUE_ENC = 1;
private static final int FALSE_ENC = 2;
private IJavaMethodDescriptor _md;
private boolean _forceHidden;
private IParameterInfo[] _params;
private IParameterInfo[] _typedParams;
private IType _retType;
private IMethodCallHandler _callHandler;
private IGenericTypeVariable[] _typeVars;
private int _staticCache = UNINITED;
private List<IExceptionInfo> _exceptions;
private IDocRef<IMethodNode> _methodDocs = new IDocRef<IMethodNode>() {
@Override
public IMethodNode get() {
if (getContainer() instanceof JavaTypeInfo) {
IClassDocNode classDocs = ((JavaTypeInfo) getContainer()).getDocNode().get();
return classDocs == null ? null : classDocs.getMethod(_md);
} else {
return null;
}
}
};
private String _name;
private String _signature;
/**
* @param container Typically this will be the containing ITypeInfo
* @param md The method descriptor (from BeanInfo)
*/
public JavaMethodInfo(IFeatureInfo container, IJavaMethodDescriptor md, boolean forceHidden) {
super(container);
_md = md;
_forceHidden = forceHidden;
_name = _md.getName();
if (_md.getMethod().isAnnotationPresent( PublishedName.class)) {
_name = (String) _md.getMethod().getAnnotation( PublishedName.class).getFieldValue("value");
}
_signature = makeSignature();
}
@Override
public IParameterInfo[] getGenericParameters()
{
return getParameters( true );
}
@Override
public IParameterInfo[] getParameters()
{
IType ownerType = getOwnersType();
return getParameters( !ownerType.isGenericType() || ownerType.isParameterizedType() );
}
private IParameterInfo[] getParameters( boolean bKeepTypeVars )
{
if (bKeepTypeVars) {
if (_typedParams == null) {
_typedParams = convertParameterDescriptors( bKeepTypeVars );
}
return _typedParams;
} else {
if (_params == null) {
_params = convertParameterDescriptors( bKeepTypeVars );
}
return _params;
}
}
@Override
public IType getGenericReturnType()
{
return getReturnType( true );
}
@Override
public IType getReturnType()
{
IType ownerType = getOwnersType();
return getReturnType( !ownerType.isGenericType() || ownerType.isParameterizedType() );
}
private IType getReturnType( boolean bKeepTypeVars )
{
if( _retType != null && !bKeepTypeVars )
{
return _retType;
}
IType declaringClass = _md.getMethod().getEnclosingClass().getJavaType();
TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName( getOwnersType(), declaringClass, bKeepTypeVars );
actualParamByVarName = addEnclosingTypeParams( declaringClass, actualParamByVarName );
for( IGenericTypeVariable tv : getTypeVariables() )
{
if( actualParamByVarName.isEmpty() )
{
actualParamByVarName = new TypeVarToTypeMap();
}
if( bKeepTypeVars )
{
actualParamByVarName.put( tv.getTypeVariableDefinition().getType(),
tv.getTypeVariableDefinition() != null
? tv.getTypeVariableDefinition().getType()
: new TypeVariableType( getOwnersType(), tv ) );
}
else
{
actualParamByVarName.put( tv.getTypeVariableDefinition().getType(), tv.getBoundingType() );
}
}
IType retType = ClassInfoUtil.getActualReturnType(_md.getMethod().getGenericReturnType(), actualParamByVarName, bKeepTypeVars);
if (TypeSystem.isDeleted(retType)) {
return null;
}
if( retType.isGenericType() && !retType.isParameterizedType() )
{
retType = TypeLord.getDefaultParameterizedType( retType );
}
retType = ClassInfoUtil.getPublishedType(retType, _md.getMethod().getEnclosingClass());
if( !bKeepTypeVars )
{
_retType = retType;
}
return retType;
}
public static TypeVarToTypeMap addEnclosingTypeParams( IType declaringClass, TypeVarToTypeMap actualParamByVarName )
{
while( declaringClass.getEnclosingType() != null && !Modifier.isStatic( declaringClass.getModifiers() ) )
{
declaringClass = declaringClass.getEnclosingType();
IGenericTypeVariable[] typeVariables = declaringClass.getGenericTypeVariables();
if( typeVariables != null )
{
for( IGenericTypeVariable typeVariable : typeVariables )
{
if( actualParamByVarName.isEmpty() )
{
actualParamByVarName = new TypeVarToTypeMap();
}
actualParamByVarName.put( typeVariable.getTypeVariableDefinition().getType(),
typeVariable.getTypeVariableDefinition() != null
? typeVariable.getTypeVariableDefinition().getType()
: new TypeVariableType( declaringClass, typeVariable ) );
}
}
}
return actualParamByVarName;
}
@Override
public List<IAnnotationInfo> getDeclaredAnnotations() {
List<IAnnotationInfo> annotations = super.getDeclaredAnnotations();
if (getMethodDocs().get() != null && getMethodDocs().get().isDeprecated()) {
annotations.add(GosuShop.getAnnotationInfoFactory().createJavaAnnotation(makeDeprecated(getMethodDocs().get().getDeprecated()), this));
}
return annotations;
}
@Override
public IGenericTypeVariable[] getTypeVariables()
{
if( _typeVars == null )
{
_typeVars = _md.getMethod().getTypeVariables(this);
}
return _typeVars;
}
@Override
public IType getParameterizedReturnType( IType... typeParams )
{
TypeVarToTypeMap actualParamByVarName =
TypeLord.mapTypeByVarName( getOwnersType(), _md.getMethod().getEnclosingClass().getJavaType(), true );
int i = 0;
for( IGenericTypeVariable tv : getTypeVariables() )
{
if( actualParamByVarName.isEmpty() )
{
actualParamByVarName = new TypeVarToTypeMap();
}
actualParamByVarName.putByString( tv.getName(), typeParams[i++] );
}
return _md.getMethod().getGenericReturnType().getActualType(actualParamByVarName, true);
}
public IType[] getParameterizedParameterTypes( IType... typeParams )
{
return getParameterizedParameterTypes2( null, typeParams );
}
public IType[] getParameterizedParameterTypes2( IGosuClass ownersType, IType... typeParams )
{
IType ot = ownersType == null ? getOwnersType() : ownersType;
TypeVarToTypeMap actualParamByVarName =
TypeLord.mapTypeByVarName( ot, _md.getMethod().getEnclosingClass().getJavaType(), true );
int i = 0;
for( IGenericTypeVariable tv : getTypeVariables() )
{
if( actualParamByVarName.isEmpty() )
{
actualParamByVarName = new TypeVarToTypeMap();
}
actualParamByVarName.putByString( tv.getName(), typeParams[i++] );
}
return ClassInfoUtil.getActualTypes(_md.getMethod().getGenericParameterTypes(), actualParamByVarName, true);
}
// <T extends CharSequence> T[] foo( ArrayList<? extends T>[] s ) { return null; }
@Override
public TypeVarToTypeMap inferTypeParametersFromArgumentTypes( IType... argTypes )
{
return inferTypeParametersFromArgumentTypes2( null, argTypes );
}
@Override
public TypeVarToTypeMap inferTypeParametersFromArgumentTypes2( IGosuClass ownersType, IType... argTypes )
{
IJavaClassType[] genParamTypes = _md.getMethod().getGenericParameterTypes();
IType ot = ownersType == null ? getOwnersType() : ownersType;
TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName( ot, _md.getMethod().getEnclosingClass().getJavaType(), true );
IGenericTypeVariable[] typeVars = getTypeVariables();
for( IGenericTypeVariable tv : typeVars )
{
if( actualParamByVarName.isEmpty() )
{
actualParamByVarName = new TypeVarToTypeMap();
}
actualParamByVarName.put( tv.getTypeVariableDefinition().getType(), tv.getBoundingType() );
}
TypeVarToTypeMap map = new TypeVarToTypeMap();
for( int i = 0; i < argTypes.length; i++ )
{
if( genParamTypes.length > i )
{
IType argType = argTypes[i];
IJavaClassType genParamType = genParamTypes[i];
inferTypeVariableTypesFromGenParamTypeAndConcreteType( genParamType, argType, map );
ensureInferredTypeAssignableToBoundingType( actualParamByVarName, map );
}
}
return map;
}
private void ensureInferredTypeAssignableToBoundingType( TypeVarToTypeMap actualParamByVarName, TypeVarToTypeMap map )
{
for( Object s : map.keySet() )
{
IType inferredType = map.getRaw( s );
IType boundingType = actualParamByVarName.getRaw( s );
if( !boundingType.isAssignableFrom( inferredType ) )
{
map.putRaw( s, boundingType );
}
}
}
private void inferTypeVariableTypesFromGenParamTypeAndConcreteType( IJavaClassType genParamType, IType argType, TypeVarToTypeMap map )
{
if( argType == GosuParserTypes.NULL_TYPE() )
{
return;
}
if( genParamType instanceof IJavaClassGenericArrayType)
{
//## todo: DON'T allow a null component type here; we do it now as a hack that enables gosu arrays to be compatible with java arrays
//## todo: same as TypeLord.inferTypeVariableTypesFromGenParamTypeAndConcreteType()
if( argType.getComponentType() == null || !argType.getComponentType().isPrimitive() )
{
inferTypeVariableTypesFromGenParamTypeAndConcreteType(
((IJavaClassGenericArrayType)genParamType).getGenericComponentType(), argType.getComponentType(), map );
}
}
else if( genParamType instanceof IJavaClassParameterizedType)
{
IJavaClassParameterizedType parameterizedType = (IJavaClassParameterizedType)genParamType;
IType argTypeInTermsOfParamType = TypeLord.findParameterizedType( argType, genParamType.getActualType( TypeVarToTypeMap.EMPTY_MAP ) );
if( argTypeInTermsOfParamType == null )
{
return;
}
IType[] concreteTypeParams = argTypeInTermsOfParamType.getTypeParameters();
if( concreteTypeParams != null && concreteTypeParams.length > 0 )
{
int i = 0;
for( IJavaClassType typeArg : parameterizedType.getActualTypeArguments() )
{
inferTypeVariableTypesFromGenParamTypeAndConcreteType( typeArg, concreteTypeParams[i++], map );
}
}
}
else if( genParamType instanceof IJavaClassTypeVariable)
{
String strTypeVarName = genParamType.getName();
IType type = map.getByString( strTypeVarName );
if( type == null || type instanceof TypeVariableType )
{
// Infer the type
map.putByString( strTypeVarName, argType );
}
}
else if( genParamType instanceof IJavaClassWildcardType)
{
IJavaClassWildcardType wildcardType = (IJavaClassWildcardType)genParamType;
inferTypeVariableTypesFromGenParamTypeAndConcreteType(
wildcardType.getUpperBound(), argType, map );
}
}
@Override
public IMethodCallHandler getCallHandler()
{
if( _callHandler != null )
{
return _callHandler;
}
IJavaClassMethod method = this._md.getMethod();
if (!(method instanceof MethodJavaClassMethod)) {
return null;
}
return _callHandler = new MethodCallAdapter( ((MethodJavaClassMethod)method).getJavaMethod() );
}
@Override
public String getReturnDescription()
{
return getMethodDocs().get() == null ? "" : getMethodDocs().get().getReturnDescription();
}
@Override
public List<IExceptionInfo> getExceptions()
{
if( _exceptions == null )
{
IJavaClassMethod method = _md.getMethod();
IJavaClassInfo[] classes = method.getExceptionTypes();
_exceptions = new ArrayList<IExceptionInfo>();
for (int i = 0; i < classes.length; i++) {
final IJavaClassInfo exceptionClass = classes[i];
_exceptions.add(new JavaExceptionInfo(this, exceptionClass, new IDocRef<IExceptionNode>() {
@Override
public IExceptionNode get() {
return getMethodDocs().get() == null ? null : getMethodDocs().get().getException(exceptionClass);
}
}));
}
}
// merge in methods exceptions with the annotations
return _exceptions;
}
@Override
public String getName() {
return _signature;
}
private String makeSignature() {
String name = getDisplayName();
name += TypeInfoUtil.getTypeVarList( this, true );
name += "(";
IParameterInfo[] parameterInfos = getGenericParameters();
if (parameterInfos.length > 0) {
name += " ";
for (int i = 0; i < parameterInfos.length; i++) {
IParameterInfo iParameterInfo = parameterInfos[i];
if (i != 0) {
name += ", ";
}
name += iParameterInfo.getFeatureType().getName();
}
name += " ";
}
name += ")";
return name;
}
@Override
public String getDisplayName()
{
return _name;
}
@Override
public String getShortDescription()
{
return getMethodDocs().get() == null ? null : getMethodDocs().get().getDescription();
}
@Override
public String getDescription()
{
return getMethodDocs().get() == null ? null : getMethodDocs().get().getDescription();
}
@Override
public boolean isHidden() {
return _forceHidden || super.isHidden();
}
@Override
protected boolean isDefaultEnumFeature()
{
if( getOwnersType().isEnum() )
{
String name = getName();
return isStatic() && (name.equals( "values()" ) || name.equals( "valueOf( java.lang.String )" ));
}
else
{
return false;
}
}
@Override
public boolean isVisible(IScriptabilityModifier constraint) {
return !_forceHidden && super.isVisible(constraint);
}
@Override
public boolean isStatic()
{
if( _staticCache == UNINITED )
{
synchronized( this )
{
if( _staticCache == UNINITED )
{
if( Modifier.isStatic( _md.getMethod().getModifiers() ) )
{
_staticCache = TRUE_ENC;
}
else
{
_staticCache = FALSE_ENC;
}
}
}
}
return _staticCache == TRUE_ENC;
}
@Override
public boolean isPrivate()
{
return Modifier.isPrivate( _md.getMethod().getModifiers() );
}
@Override
public boolean isInternal()
{
return !isPrivate() && !isPublic() && !isProtected();
}
@Override
public boolean isProtected()
{
return Modifier.isProtected( _md.getMethod().getModifiers() );
}
@Override
public boolean isPublic()
{
return Modifier.isPublic( _md.getMethod().getModifiers() );
}
@Override
public boolean isAbstract()
{
return Modifier.isAbstract( _md.getMethod().getModifiers() );
}
@Override
public boolean isFinal()
{
return Modifier.isFinal( _md.getMethod().getModifiers() );
}
@Override
public boolean isDeprecated()
{
return isJavadocDeprecated() || super.isDeprecated() || getMethod().isAnnotationPresent( Deprecated.class ) || getMethod().isAnnotationPresent( java.lang.Deprecated.class );
}
private boolean isJavadocDeprecated()
{
return (getModifiers() & Opcodes.ACC_DEPRECATED) > 0;
}
@Override
public String getDeprecatedReason() {
String deprecated = super.getDeprecatedReason();
if (isDeprecated() && deprecated == null) {
IAnnotationInfo gwDeprecated = getMethod().getAnnotation( Deprecated.class );
return gwDeprecated == null ? null : (String) gwDeprecated.getFieldValue( "value" );
}
return deprecated;
}
private IParameterInfo[] convertParameterDescriptors( boolean bKeepTypeVars )
{
IType declaringClass = _md.getMethod().getEnclosingClass().getJavaType();
TypeVarToTypeMap actualParamByVarName =
TypeLord.mapTypeByVarName( getOwnersType(), declaringClass, bKeepTypeVars );
actualParamByVarName = addEnclosingTypeParams( declaringClass, actualParamByVarName );
for( IGenericTypeVariable tv : getTypeVariables() )
{
if( actualParamByVarName.isEmpty() )
{
actualParamByVarName = new TypeVarToTypeMap();
}
if( bKeepTypeVars )
{
actualParamByVarName.put( tv.getTypeVariableDefinition().getType(),
tv.getTypeVariableDefinition() != null
? tv.getTypeVariableDefinition().getType()
: new TypeVariableType( getOwnersType(), tv ) );
}
else
{
actualParamByVarName.put( tv.getTypeVariableDefinition().getType(), tv.getBoundingType() );
}
}
IJavaClassType[] paramTypes = _md.getMethod().getGenericParameterTypes();
return convertGenericParameterTypes( this, actualParamByVarName, paramTypes, bKeepTypeVars, _md.getMethod().getEnclosingClass());
}
static IParameterInfo[] convertGenericParameterTypes( IFeatureInfo container,
TypeVarToTypeMap actualParamByVarName,
IJavaClassType[] paramTypes,
boolean bKeepTypeVars,
IJavaClassInfo declaringClass )
{
if( paramTypes == null )
{
return null;
}
IParameterInfo[] pi = new IParameterInfo[paramTypes.length];
for( int i = 0; i < paramTypes.length; i++ )
{
IType parameterType = null;
if(paramTypes[i] != null) {
parameterType = paramTypes[i].getActualType( actualParamByVarName, bKeepTypeVars );
}
if (parameterType == null) {
parameterType = TypeSystem.getErrorType();
}
parameterType = ClassInfoUtil.getPublishedType(parameterType, declaringClass);
pi[i] = new SimpleParameterInfo( container, parameterType, i );
}
return pi;
}
@Override
public IJavaClassMethod getMethod()
{
return _md.getMethod();
}
@Override
public String toString()
{
return getName();
}
@Override
protected IJavaAnnotatedElement getAnnotatedElement()
{
return _md.getMethod();
}
@Override
protected boolean isVisibleViaFeatureDescriptor(IScriptabilityModifier constraint) {
return _md.isVisibleViaFeatureDescriptor(constraint);
}
@Override
protected boolean isHiddenViaFeatureDescriptor() {
return _md.isHiddenViaFeatureDescriptor();
}
@Override
public IDocRef<IParamNode> getDocsForParam(final int paramIndex) {
return new IDocRef<IParamNode>() {
@Override
public IParamNode get() {
if (getMethodDocs().get() != null) {
List<? extends IParamNode> list = getMethodDocs().get().getParams();
if (list.size() > paramIndex) {
return list.get(paramIndex);
}
}
return null;
}
};
}
@Override
public IDocRef<IMethodNode> getMethodDocs() {
return _methodDocs;
}
@Override
public Method getRawMethod() {
return ((MethodJavaClassMethod)_md.getMethod()).getJavaMethod();
}
@Override
public int getModifiers() {
return _md.getMethod().getModifiers();
}
}