package com.redhat.ceylon.model.loader.impl.reflect.mirror;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.redhat.ceylon.model.loader.mirror.AnnotationMirror;
import com.redhat.ceylon.model.loader.mirror.ClassMirror;
import com.redhat.ceylon.model.loader.mirror.MethodMirror;
import com.redhat.ceylon.model.loader.mirror.TypeMirror;
import com.redhat.ceylon.model.loader.mirror.TypeParameterMirror;
import com.redhat.ceylon.model.loader.mirror.VariableMirror;
public class ReflectionMethod implements MethodMirror {
public final Member method;
private ArrayList<VariableMirror> parameters;
private List<TypeParameterMirror> typeParameters;
private Boolean overridingMethod;
private Boolean overloadingMethod;
private ReflectionType returnType;
private ClassMirror enclosingClass;
private Map<String, AnnotationMirror> annotations;
public ReflectionMethod(ClassMirror enclosingClass, Member method) {
this.method = method;
this.enclosingClass = enclosingClass;
}
@Override
public AnnotationMirror getAnnotation(String type) {
return getAnnotations().get(type);
}
private Map<String, AnnotationMirror> getAnnotations() {
// profiling revealed we need to cache this
if(annotations == null){
annotations = ReflectionUtils.getAnnotations((AnnotatedElement)method);
}
return annotations;
}
@Override
public String getName() {
return method.getName();
}
@Override
public boolean isStatic() {
return Modifier.isStatic(method.getModifiers());
}
@Override
public boolean isPublic() {
return Modifier.isPublic(method.getModifiers());
}
@Override
public boolean isProtected() {
return Modifier.isProtected(method.getModifiers());
}
@Override
public boolean isDefaultAccess() {
return !Modifier.isPrivate(method.getModifiers())
&& !Modifier.isPublic(method.getModifiers())
&& !Modifier.isProtected(method.getModifiers());
}
@Override
public boolean isConstructor() {
return method instanceof Constructor;
}
@Override
public boolean isStaticInit() {
return false;
}
@Override
public boolean isVariadic() {
return method instanceof Method ?
((Method)method).isVarArgs()
: ((Constructor<?>)method).isVarArgs();
}
@Override
public List<VariableMirror> getParameters() {
if(parameters != null)
return parameters;
Type[] javaParameters;
Annotation[][] annotations;
int parameterCount;
if(method instanceof Method){
javaParameters = ((Method)method).getGenericParameterTypes();
annotations = ((Method)method).getParameterAnnotations();
// only getParameterTypes always reliably include synthetic parameters for constructors
parameterCount = ((Method)method).getParameterTypes().length;
}else{
javaParameters = ((Constructor<?>)method).getGenericParameterTypes();
annotations = ((Constructor<?>)method).getParameterAnnotations();
// only getParameterTypes always reliably include synthetic parameters for constructors
parameterCount = ((Constructor<?>)method).getParameterTypes().length;
}
parameters = new ArrayList<VariableMirror>(parameterCount);
int start = 0;
if(method instanceof Constructor){
// enums will always add two synthetic parameters (string and int) and always be static so none more
Class<?> declaringClass = method.getDeclaringClass();
if(declaringClass.isEnum())
start = 2;
// inner classes will always add a synthetic parameter to the constructor, unless they are static
// FIXME: local and anonymous classes may add more but we don't know how to find out
else if((declaringClass.isMemberClass()
|| declaringClass.isAnonymousClass()
// if it's a local class its container method must not be static
|| (declaringClass.isLocalClass() && !isStaticLocalContainer(declaringClass)))
&& !Modifier.isStatic(declaringClass.getModifiers()))
start = 1;
}
// some compilers will only include non-synthetic parameters in getGenericParameterTypes(), so we need to know if
// we have less, we should subtract synthetic parameters
int parametersOffset = javaParameters.length != parameterCount ? -start : 0;
// if at least one parameter is annotated, java reflection will only include non-synthetic parameters in
// getParameterAnnotations(), so we need to know if we have less, we should subtract synthetic parameters
int annotationsOffset = annotations.length != parameterCount ? -start : 0;
// we have synthetic parameters first (skipped with start), then regular params, then synthetic captured params
// if we have any synthetic params, remove them from the count, except the ones from the start
// this makes sure we don't consider synthetic captured params
if(javaParameters.length != parameterCount)
parameterCount = javaParameters.length + start;
else if(annotations.length != parameterCount) // better luck with annotations?
parameterCount = annotations.length + start;
// skip synthetic parameters
for(int i=start;i<parameterCount;i++){
// apply offsets for parameters and annotations if synthetic parameters are not included
parameters.add(new ReflectionVariable(javaParameters[i+parametersOffset], annotations[i+annotationsOffset]));
}
return parameters;
}
private boolean isStaticLocalContainer(Class<?> klass) {
Constructor<?> enclosingConstructor = klass.getEnclosingConstructor();
if(enclosingConstructor != null)
return Modifier.isStatic(enclosingConstructor.getModifiers());
Method enclosingMethod = klass.getEnclosingMethod();
return Modifier.isStatic(enclosingMethod.getModifiers());
}
@Override
public boolean isAbstract() {
return Modifier.isAbstract(method.getModifiers());
}
@Override
public boolean isFinal() {
return Modifier.isFinal(method.getModifiers());
}
@Override
public TypeMirror getReturnType() {
if(returnType != null)
return returnType;
if (method instanceof Method) {
returnType = new ReflectionType(((Method)method).getGenericReturnType());
} else {
returnType = new ReflectionType(((Constructor<?>)method).getDeclaringClass());
}
return returnType;
}
@Override
public boolean isDeclaredVoid() {
return method instanceof Method
&& Void.TYPE == ((Method)method).getReturnType();
}
@Override
public List<TypeParameterMirror> getTypeParameters() {
if(typeParameters != null)
return typeParameters;
typeParameters = ReflectionUtils.getTypeParameters((GenericDeclaration) method);
return typeParameters;
}
public boolean isOverridingMethod() {
if(overridingMethod != null)
return overridingMethod.booleanValue();
if(method instanceof Method)
overridingMethod = ReflectionUtils.isOverridingMethod((Method) method);
else
overridingMethod = false;
return overridingMethod;
}
public boolean isOverloadingMethod() {
if(overloadingMethod != null)
return overloadingMethod.booleanValue();
if(method instanceof Method)
overloadingMethod = ReflectionUtils.isOverloadingMethod((Method) method);
else
overloadingMethod = false;
return overloadingMethod;
}
@Override
public String toString() {
return "[ReflectionMethod: "+method.toString()+"]";
}
@Override
public boolean isDefault() {
return ((Method)method).getDefaultValue() != null;
}
@Override
public ClassMirror getEnclosingClass() {
return enclosingClass;
}
}