package spoon.test;
import spoon.Launcher;
import spoon.SpoonAPI;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.filter.OverridingMethodFilter;
import spoon.support.DerivedProperty;
import java.util.ArrayList;
import java.util.List;
public class SpoonTestHelpers {
// only static methods
private SpoonTestHelpers(){
}
public static List<CtType<? extends CtElement>> getAllInstantiableMetamodelInterfaces() {
List<CtType<? extends CtElement>> result = new ArrayList<>();
SpoonAPI interfaces = new Launcher();
interfaces.addInputResource("src/main/java/spoon/reflect/declaration");
interfaces.addInputResource("src/main/java/spoon/reflect/code");
interfaces.addInputResource("src/main/java/spoon/reflect/reference");
interfaces.buildModel();
SpoonAPI implementations = new Launcher();
implementations.addInputResource("src/main/java/spoon/support/reflect/declaration");
implementations.addInputResource("src/main/java/spoon/support/reflect/code");
implementations.addInputResource("src/main/java/spoon/support/reflect/reference");
implementations.buildModel();
for(CtType<? > itf : interfaces.getModel().getAllTypes()) {
String impl = itf.getQualifiedName().replace("spoon.reflect", "spoon.support.reflect")+"Impl";
CtType implClass = implementations.getFactory().Type().get(impl);
if (implClass != null && !implClass.hasModifier(ModifierKind.ABSTRACT)) {
result.add((CtType<? extends CtElement>) itf);
}
}
return result;
}
/**
* returns true if typeReference point to a class of the metamodel or a List/set of a class of the metamodel.
*/
public static boolean isMetamodelRelatedType(CtTypeReference<?> typeReference) {
CtTypeReference<Object> ctElRef = typeReference.getFactory().Code().createCtTypeReference(CtElement.class);
// simple case, a sublcass of CtElement
if (typeReference.isSubtypeOf(ctElRef)) {
return true;
}
// limit case because of a bug to be fixed
if (typeReference.getActualTypeArguments().size()>0 && "?".equals(typeReference.getActualTypeArguments()
.get(0).getQualifiedName())) {
return false;
}
return (typeReference.getActualTypeArguments().size()>0
&& typeReference.getActualTypeArguments()
.get(0).getTypeDeclaration()
.isSubtypeOf(ctElRef))
;
}
/**
* The default contains based on Method.equals takes into account the return type
* And we don't want this, because we need to capture
* the annotation of the implementation method.
*/
private static boolean containsMethodBasedOnName(List<CtMethod<?>> l, CtMethod setter) {
for(CtMethod<?> m : l ) {
if (m.getSimpleName().equals(setter.getSimpleName())) {
return true;
}
}
return false;
}
/** returns all possible methods in the order class then interface, and up again */
public static List<CtMethod<?>> getAllMetamodelMethods(CtType<?> baseType) {
List<CtMethod<?>> result = new ArrayList<>();
for (CtMethod<?> m : baseType.getMethods()) {
if (!containsMethodBasedOnName(result, m)) {
result.add(m);
}
}
for (CtTypeReference<?> itf : baseType.getSuperInterfaces()) {
for (CtMethod<?> up : getAllSetters(itf.getTypeDeclaration())) {
if (!containsMethodBasedOnName(result, up)) {
result.add(up);
}
}
}
return result;
}
/** returns all possible setters related to CtElement */
public static List<CtMethod<?>> getAllSetters(CtType<?> baseType) {
List<CtMethod<?>> result = new ArrayList<>();
for (CtMethod<?> m : getAllMetamodelMethods(baseType)) {
if("setParent".equals(m.getSimpleName())) {
//parent is a special kind of setter, which does not influence model properties of element, but link to parent element.
continue;
}
if (!m.getSimpleName().startsWith("set") && !m.getSimpleName().startsWith("set")) {
continue;
}
if (m.getParameters().size()!=1) {
continue;
}
if (!isMetamodelRelatedType(m.getParameters().get(0).getType())) {
continue;
}
result.add(m);
}
return result;
}
/** returns the corresponding setter, if several are possible returns the lowest one in the hierarchy */
public static CtMethod<?> getSetterOf(CtType<?> baseType, CtMethod<?> getter) {
String setterName = getter.getSimpleName().replaceFirst("^get", "set");
Object[] tentativeSetters = baseType.getAllMethods().stream().filter(x->x.getSimpleName().equals(setterName)).toArray();
if (tentativeSetters.length==0) {
return null;
}
// return one that is as low as possible in the hierarchy
for(Object o : tentativeSetters) {
if (baseType.getPackage().getElements(new OverridingMethodFilter((CtMethod<?>) o)).size() == 0) {
return (CtMethod<?>) o;
}
}
//System.out.println(setterName+" "+tentativeSetters.length);
return (CtMethod<?>) tentativeSetters[0];
}
/** specifies what a metamodel property is: a getter than returns a metamodel-related class and that is not derived */
public static boolean isMetamodelProperty(CtType<?> baseType, CtMethod<?> m) {
return
m.getSimpleName().startsWith("get")
&& m.getParameters().size() == 0 // a getter has no parameter
&& m.getAnnotation(DerivedProperty.class) == null
&&
// return type
isMetamodelRelatedType(m.getType());
}
}