package io.micronaut.annotation.processing;
import io.micronaut.annotation.processing.visitor.JavaVisitorContext;
import io.micronaut.inject.processing.JavaModelUtils;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.*;
import javax.lang.model.util.AbstractTypeVisitor8;
import javax.lang.model.util.Types;
import java.lang.reflect.AnnotatedType;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public abstract class SuperclassAwareTypeVisitor<R, P> extends AbstractTypeVisitor8<R, P> {
private final Set<String> processed = new HashSet<>();
private final Types types;
private final GenericUtils genericUtils;
private final ModelUtils modelUtils;
protected SuperclassAwareTypeVisitor(JavaVisitorContext visitorContext) {
this.types = visitorContext.getTypes();
this.genericUtils = visitorContext.getGenericUtils();
this.modelUtils = visitorContext.getModelUtils();
}
@Override
public R visitDeclared(DeclaredType type, P p) {
final Element element = type.asElement();
if ((JavaModelUtils.isClassOrInterface(element) || JavaModelUtils.isEnum(element)) &&
!element.toString().equals(Object.class.getName()) &&
!element.toString().equals(Enum.class.getName())) {
TypeElement typeElement = (TypeElement) element;
List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
for (Element enclosedElement : enclosedElements) {
boolean isAcceptable = isAcceptable(enclosedElement);
if (isAcceptable) {
String qualifiedName;
if (enclosedElement instanceof ExecutableElement) {
qualifiedName = buildQualifiedName(type, typeElement, (ExecutableElement) enclosedElement);
} else {
qualifiedName = types.erasure(enclosedElement.asType()).toString() + "." + enclosedElement.getSimpleName().toString();
}
if (!processed.contains(qualifiedName)) {
processed.add(qualifiedName);
accept(type, enclosedElement, p);
}
} else if (enclosedElement instanceof ExecutableElement) {
ExecutableElement ee = (ExecutableElement) enclosedElement;
Set<Modifier> modifiers = ee.getModifiers();
if (!modifiers.contains(Modifier.PRIVATE) && !modifiers.contains(Modifier.STATIC)) {
String qualifiedName = buildQualifiedName(type, typeElement, ee);
processed.add(qualifiedName);
}
}
}
TypeMirror superMirror = typeElement.getSuperclass();
if (superMirror instanceof DeclaredType) {
visitDeclared((DeclaredType) superMirror, p);
}
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
for (TypeMirror anInterface : interfaces) {
if (anInterface instanceof DeclaredType) {
DeclaredType interfaceType = (DeclaredType) anInterface;
visitDeclared(interfaceType, p);
}
}
}
return null;
}
protected abstract boolean isAcceptable(Element element);
protected abstract void accept(DeclaredType type, Element element, P p);
@Override
public R visitIntersection(IntersectionType t, P p) {
return null;
}
@Override
public R visitPrimitive(PrimitiveType t, P p) {
return null;
}
@Override
public R visitNull(NullType t, P p) {
return null;
}
@Override
public R visitArray(ArrayType t, P p) {
return null;
}
@Override
public R visitError(ErrorType t, P p) {
return null;
}
@Override
public R visitTypeVariable(TypeVariable t, P p) {
return null;
}
@Override
public R visitWildcard(WildcardType t, P p) {
return null;
}
@Override
public R visitExecutable(ExecutableType t, P p) {
return null;
}
@Override
public R visitNoType(NoType t, P p) {
return null;
}
@Override
public R visitUnion(UnionType t, P p) {
return null;
}
private String buildQualifiedName(DeclaredType type, TypeElement typeElement, ExecutableElement enclosedElement) {
ExecutableElement ee = enclosedElement;
String qualifiedName = ee.getSimpleName().toString();
qualifiedName += "(" + ee.getParameters().stream().map(variableElement -> types.erasure(variableElement.asType()).toString()).collect(Collectors.joining(",")) + ")";
TypeMirror returnTypeMirror = ee.getReturnType();
String returnType = null;
if (returnTypeMirror.getKind() == TypeKind.TYPEVAR) {
Map<String, Object> generics = genericUtils.buildGenericTypeArgumentInfo(type)
.get(typeElement.getQualifiedName().toString());
if (generics != null && generics.containsKey(returnTypeMirror.toString())) {
returnType = generics.get(returnTypeMirror.toString()).toString();
}
}
if (returnType == null) {
returnType = modelUtils.resolveTypeName(returnTypeMirror);
}
qualifiedName = returnType + "." + qualifiedName;
return qualifiedName;
}
}