package com.redhat.ceylon.compiler.java.test.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import org.junit.Assert;
import org.junit.Test;
import com.redhat.ceylon.compiler.java.metadata.Ceylon;
import com.redhat.ceylon.compiler.java.metadata.Ignore;
import com.redhat.ceylon.compiler.java.metadata.TypeInfo;
import com.redhat.ceylon.compiler.java.metadata.ValueType;
public class ValueTypeTest {
@Test
public void testValueTypes() {
validateValueType(ceylon.language.Boolean.class);
validateValueType(ceylon.language.Byte.class);
validateValueType(ceylon.language.Character.class);
validateValueType(ceylon.language.Float.class);
validateValueType(ceylon.language.Integer.class);
validateValueType(ceylon.language.String.class);
validateValueType(com.redhat.ceylon.compiler.java.language.BooleanArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.ByteArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.CharArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.DoubleArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.FloatArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.IntArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.LongArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.ObjectArray.class);
validateValueType(com.redhat.ceylon.compiler.java.language.ShortArray.class);
}
private void validateValueType(Class<?> clazz) {
String className = clazz.getName();
Assert.assertTrue(className + " not a Ceylon class", isCeylonClass(clazz));
Assert.assertTrue(className + " not a Value Type", isValueType(clazz));
Method[] methods = clazz.getMethods();
for (Method m : methods) {
validateVTMethod(clazz, m);
}
}
private boolean isCeylonClass(Class<?> clazz) {
return clazz.getAnnotation(Ceylon.class) != null;
}
private boolean isValueType(Class<?> clazz) {
return clazz.getAnnotation(ValueType.class) != null;
}
// Check if the given method should have a static companion method
// and if so check that it exists and adheres to all the rules
private void validateVTMethod(Class<?> clazz, Method classMethod) {
if (classMethod.getAnnotation(Ignore.class) != null
|| (classMethod.getModifiers() & Modifier.STATIC) != 0
|| (classMethod.getModifiers() & Modifier.VOLATILE) != 0
|| classMethod.getDeclaringClass() == Object.class) {
// We skip static methods and the ones marked with @Ignore
// We also skip "volatile" methods, whatever they are
// And finally we skip all methods defined on Object
return;
}
validateVTMethodUnchecked(clazz, classMethod);
// See if it has any overloads that need checking
Method[] overloads = findVTMethodOverloads(clazz, classMethod);
for (Method m : overloads) {
if (m.getAnnotation(Ignore.class) == null
|| (m.getModifiers() & Modifier.STATIC) != 0
|| (m.getModifiers() & Modifier.VOLATILE) != 0) {
// We skip static methods and "volatile" methods
// and those NOT marked with @Ignore
continue;
}
validateVTMethodUnchecked(clazz, m);
}
}
// Check if the given method should have a static companion method
// and if so check that it exists and adheres to all the rules
private void validateVTMethodUnchecked(Class<?> clazz, Method classMethod) {
String mthName = clazz.getName() + "::" + classMethod.getName() + "()";
System.err.println(mthName);
Method staticMethod = findStaticCompanionMethod(clazz, classMethod);
Assert.assertNotNull("Static companion for " + mthName + " not found", staticMethod);
TypeInfo returnTypeInfo = classMethod.getAnnotation(TypeInfo.class);
Assert.assertEquals("Returns types for static and class methods " + mthName + " do not coincide", staticMethod.getReturnType(), getUnboxedType(classMethod.getReturnType(), returnTypeInfo));
}
// Tries to find the companion method for the given class method
// by looking for a static method with the same parameters plus
// an extra first parameter. Also the static version will always
// have the "unboxed" version of any Value Type that appears as
// either parameter or return type
private Method findStaticCompanionMethod(Class<?> clazz, Method classMethod) {
Class<?>[] instancePTs = classMethod.getParameterTypes();
Annotation[][] instanceAnnos = classMethod.getParameterAnnotations();
Class<?>[] staticPTs = new Class<?>[instancePTs.length + 1];
staticPTs[0] = getUnboxedType(clazz, clazz.getAnnotation(TypeInfo.class));
for (int i = 0; i < instancePTs.length; i++) {
TypeInfo typeInfo = findAnnotation(instanceAnnos[i], TypeInfo.class);
staticPTs[i + 1] = getUnboxedType(instancePTs[i], typeInfo);
}
try {
return clazz.getMethod(classMethod.getName(), staticPTs);
} catch (NoSuchMethodException|SecurityException e) {
return null;
}
}
@SuppressWarnings("unchecked")
private <T> T findAnnotation(Annotation[] annotations, Class<T> annoClass) {
for (Annotation a : annotations) {
if (a.annotationType() == annoClass) {
return (T)a;
}
}
return null;
}
// Given a type this method returns the unboxed version.
// For this the type must be a Ceylon Value Type. It's "unboxed" type
// will be taken from the parameter of its instance() method.
// Also if the type has associated TypeInfo the type it defines
// must be exactly the type of the Value Type itself otherwise
// no unboxing will be performed
private Class<?> getUnboxedType(Class<?> clazz, TypeInfo typeInfo) {
if (isCeylonClass(clazz) && isValueType(clazz)) {
if (typeInfo != null) {
// If we have Ceylon type information available we will only
// unbox if it's exactly the same as the Value Type itself
String type = typeInfo.value().replace("::", ".");
if (!clazz.getName().equals(type)) {
return clazz;
}
}
Method instanceMethod = findInstanceMethod(clazz);
Assert.assertNotNull("Required static method instance() for " + clazz.getName() + " not found", instanceMethod);
Class<?>[] pts = instanceMethod.getParameterTypes();
if (pts.length == 1) {
return pts[0];
}
}
return clazz;
}
// Tries to find overloads of the given method
private Method[] findVTMethodOverloads(Class<?> clazz, Method classMethod) {
ArrayList<Method> overrides = new ArrayList<Method>();
Method[] methods = clazz.getMethods();
for (Method m : methods) {
if (!m.equals(classMethod)
&& m.getName().equals(classMethod.getName())
&& (m.getModifiers() & Modifier.STATIC) == 0) {
overrides.add(m);
}
}
Method[] result = new Method[overrides.size()];
return overrides.toArray(result);
}
// Finds the instance() method of a Value Type
private Method findInstanceMethod(Class<?> clazz) {
Method[] methods = clazz.getMethods();
for (Method m : methods) {
if ("instance".equals(m.getName()) && (m.getModifiers() & Modifier.STATIC) != 0) {
return m;
}
}
return null;
}
}