package io.vertx.codegen;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.type.ClassKind;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Helper {
public static final Function<Element, Stream<ExecutableElement>> FILTER_METHOD = element -> {
if (element.getKind() == ElementKind.METHOD) {
return Stream.of((ExecutableElement) element);
} else {
return Stream.empty();
}
};
public static final Function<Element, Stream<VariableElement>> FILTER_FIELD = element -> {
if (element.getKind() == ElementKind.FIELD) {
return Stream.of((VariableElement) element);
} else {
return Stream.empty();
}
};
static <T> Function<Object, Stream<T>> instanceOf(Class<T> type) {
return o -> {
if (type.isInstance(o)) {
return Stream.of(type.cast(o));
} else {
return Stream.empty();
}
};
}
static <T> Function<Object, Stream<T>> cast(Class<T> type) {
return o -> Stream.of(type.cast(o));
}
static final Function<Element, Stream<ExecutableElement>> CAST = element -> {
if (element.getKind() == ElementKind.METHOD) {
return Stream.of((ExecutableElement) element);
} else {
return Stream.empty();
}
};
public static String normalizePropertyName(String propertyName) {
if (Character.isUpperCase(propertyName.charAt(0))) {
StringBuilder buffer = new StringBuilder(propertyName);
int index = 0;
while (true) {
buffer.setCharAt(index, Character.toLowerCase(buffer.charAt(index++)));
if (index < buffer.length() && Character.isUpperCase(buffer.charAt(index))) {
if (index + 1 < buffer.length() && Character.isLowerCase(buffer.charAt(index + 1))) {
break;
}
} else {
break;
}
}
propertyName = buffer.toString();
}
return propertyName;
}
public static String decapitaliseFirstLetter(String str) {
if (str.length() == 0) {
return str;
} else {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
}
public static String convertCamelCaseToUnderscores(String str) {
return str.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z\\d])([A-Z])", "$1_$2").toLowerCase();
}
public static String getSimpleName(String type) {
return type.substring(type.lastIndexOf('.') + 1);
}
public static String getPackageName(String type) {
int index = type.lastIndexOf('.');
if (index >= 0) {
return type.substring(0, index);
} else {
return "";
}
}
public static String getNonGenericType(String type) {
int pos = type.indexOf("<");
if (pos >= 0) {
String nonGenericType = type.substring(0, pos);
return nonGenericType;
} else {
return type;
}
}
public static String indentString(String str, String indent) {
StringBuilder sb = new StringBuilder(indent);
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
sb.append(ch);
if (ch == '\n' && i != str.length() - 1) {
sb.append(indent);
}
}
return sb.toString();
}
public static String getJavadocTag(String comment, String tagName) {
int pos = comment.indexOf(tagName);
int endPos = comment.indexOf("\n", pos);
String tag = comment.substring(pos + tagName.length() + 1, endPos);
return tag;
}
public static String removeTags(String comment) {
int pos = comment.indexOf('@');
if (pos == -1) {
return comment;
}
if (pos > 0) {
String beforePos = comment.substring(0, pos);
int prevReturn = beforePos.lastIndexOf('\n');
if (prevReturn != -1) {
pos = prevReturn;
} else {
pos = 0;
}
}
return comment.substring(0, pos);
}
public static AnnotationMirror resolveMethodAnnotation(
Class<? extends Annotation> annotationType, Elements elementUtils, Types typeUtils,
TypeElement declaring, ExecutableElement method) {
return resolveMethodAnnotation(
(DeclaredType) elementUtils.getTypeElement(annotationType.getName()).asType(),
elementUtils, typeUtils, declaring, method);
}
public static AnnotationMirror resolveMethodAnnotation(
DeclaredType annotationType, Elements elementUtils, Types typeUtils,
TypeElement declaring, ExecutableElement method) {
Optional<? extends AnnotationMirror> annotation = method.getAnnotationMirrors().stream().filter(mirror -> typeUtils.isSameType(mirror.getAnnotationType(), annotationType)).findFirst();
if (annotation.isPresent()) {
return annotation.get();
} else {
return isFluent(annotationType, elementUtils, typeUtils, declaring, method, method.getEnclosingElement().asType());
}
}
private static AnnotationMirror isFluent(DeclaredType annotationType, Elements elementUtils,
Types typeUtils, TypeElement declaring, ExecutableElement method, TypeMirror type) {
for (TypeMirror directSuperType : typeUtils.directSupertypes(type)) {
Element directSuperTypeElt = typeUtils.asElement(directSuperType);
if (directSuperTypeElt instanceof TypeElement) {
List<ExecutableElement> methods = ((TypeElement) directSuperTypeElt).getEnclosedElements().stream().
filter(member -> member.getKind() == ElementKind.METHOD).map(member -> (ExecutableElement) member).
collect(Collectors.toList());
for (ExecutableElement m : methods) {
if (elementUtils.overrides(method, m, declaring)) {
AnnotationMirror annotation = resolveMethodAnnotation(annotationType, elementUtils, typeUtils, (TypeElement) directSuperTypeElt, m);
if (annotation != null) {
return annotation;
}
}
}
AnnotationMirror annotation = isFluent(annotationType, elementUtils, typeUtils, declaring, method, directSuperType);
if (annotation != null) {
return annotation;
}
}
}
return null;
}
public static TypeMirror resolveTypeParameter(Types typeUtils, DeclaredType subType, TypeParameterElement typeParam) {
TypeMirror erased = typeUtils.erasure(typeParam.getGenericElement().asType());
TypeMirror erasedSubType = typeUtils.erasure(subType);
if (typeUtils.isSameType(erased, erasedSubType)) {
return typeUtils.asMemberOf(subType, ((TypeVariable) typeParam.asType()).asElement());
} else if (typeUtils.isSubtype(erasedSubType, erased)) {
for (TypeMirror superType : typeUtils.directSupertypes(subType)) {
TypeMirror resolved = resolveTypeParameter(typeUtils, (DeclaredType) superType, typeParam);
if (resolved != null) {
return resolved;
}
}
}
return null;
}
public static <T> Type resolveTypeParameter(Type type, java.lang.reflect.TypeVariable<java.lang.Class<T>> typeParam) {
if (type instanceof Class<?>) {
Class<?> classType = (Class<?>) type;
if (Stream.of(classType.getTypeParameters()).filter(tp -> tp.equals(typeParam)).findFirst().isPresent()) {
return typeParam;
}
List<Type> superTypes = new ArrayList<>();
if (classType.getGenericSuperclass() != null) {
superTypes.add(classType.getGenericSuperclass());
}
Collections.addAll(superTypes, classType.getGenericInterfaces());
for (Type superType : superTypes) {
Type resolved = resolveTypeParameter(superType, typeParam);
if (resolved != null) {
return resolved;
}
}
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
Type resolvedType = resolveTypeParameter(rawType, typeParam);
if (resolvedType instanceof java.lang.reflect.TypeVariable<?>) {
GenericDeclaration owner = ((java.lang.reflect.TypeVariable) resolvedType).getGenericDeclaration();
if (owner.equals(rawType)) {
java.lang.reflect.TypeVariable<?>[] typeParams = owner.getTypeParameters();
for (int i = 0;i < typeParams.length;i++) {
if (typeParams[i].equals(resolvedType)) {
return parameterizedType.getActualTypeArguments()[i];
}
}
}
}
} else {
throw new UnsupportedOperationException("Todo " + type.getTypeName() + " " + type.getClass().getName());
}
return null;
}
private static final Pattern SIGNATURE_PATTERN = Pattern.compile("#(\\p{javaJavaIdentifierStart}(?:\\p{javaJavaIdentifierPart})*)(?:\\((.*)\\))?$");
public static final Pattern LINK_REFERENCE_PATTERN = Pattern.compile(
"(?:(?:\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*" + "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)?" +
"(?:" + SIGNATURE_PATTERN.pattern() + ")?");
public static Element resolveSignature(
Elements elementUtils,
Types typeUtils,
TypeElement declaringElt,
String signature) {
Matcher signatureMatcher = SIGNATURE_PATTERN.matcher(signature);
if (signatureMatcher.find()) {
String memberName = signatureMatcher.group(1);
String typeName = signature.substring(0, signatureMatcher.start());
TypeElement typeElt = resolveTypeElement(elementUtils, declaringElt, typeName);
if (typeElt != null) {
Predicate<? super Element> memberMatcher;
if (signatureMatcher.group(2) != null) {
String t = signatureMatcher.group(2).trim();
Predicate<ExecutableElement> parametersMatcher;
if (t.length() == 0) {
parametersMatcher = exeElt -> exeElt.getParameters().isEmpty();
} else {
parametersMatcher = parametersMatcher(typeUtils, t.split("\\s*,\\s*"));
}
memberMatcher = elt -> matchesConstructor(elt, memberName, parametersMatcher) || matchesMethod(elt, memberName, parametersMatcher);
} else {
memberMatcher = elt -> matchesConstructor(elt, memberName, exeElt -> true) ||
matchesMethod(elt, memberName, exeElt -> true) ||
matchesField(elt, memberName);
}
for (ElementKind kind : Arrays.asList(ElementKind.FIELD, ElementKind.CONSTRUCTOR, ElementKind.METHOD)) {
for (Element memberElt : elementUtils.getAllMembers(typeElt)) {
if(memberElt.getKind() == kind && memberMatcher.test(memberElt)) {
return memberElt;
}
}
}
}
return null;
} else {
return resolveTypeElement(elementUtils, declaringElt, signature);
}
}
private static TypeElement resolveTypeElement(Elements elementUtils, TypeElement declaringElt, String typeName) {
TypeElement resolvedElt;
if (typeName.isEmpty()) {
resolvedElt = declaringElt;
} else {
if (typeName.lastIndexOf('.') == -1) {
resolvedElt = elementUtils.getTypeElement("java.lang." +typeName);
if (resolvedElt == null) {
String packageName = elementUtils.getPackageOf(declaringElt).getQualifiedName().toString();
resolvedElt = elementUtils.getTypeElement(packageName + '.' + typeName);
}
} else {
resolvedElt = elementUtils.getTypeElement(typeName);
}
}
return resolvedElt;
}
private static boolean matchesConstructor(Element elt, String memberName, Predicate<ExecutableElement> parametersMatcher) {
if (elt.getKind() == ElementKind.CONSTRUCTOR) {
ExecutableElement constructorElt = (ExecutableElement) elt;
TypeElement typeElt = (TypeElement) constructorElt.getEnclosingElement();
return typeElt.getSimpleName().toString().equals(memberName) && parametersMatcher.test(constructorElt);
}
return false;
}
private static boolean matchesMethod(Element elt, String memberName, Predicate<ExecutableElement> parametersMatcher) {
if (elt.getKind() == ElementKind.METHOD) {
ExecutableElement methodElt = (ExecutableElement) elt;
return methodElt.getSimpleName().toString().equals(memberName) && parametersMatcher.test(methodElt);
}
return false;
}
private static boolean matchesField(Element elt, String memberName) {
return elt.getKind() == ElementKind.FIELD && elt.getSimpleName().toString().equals(memberName);
}
private static Predicate<ExecutableElement> parametersMatcher(Types typeUtils, String[] parameterSignature) {
return exeElt -> {
if (exeElt.getParameters().size() == parameterSignature.length) {
TypeMirror tm2 = exeElt.asType();
ExecutableType tm3 = (ExecutableType) typeUtils.erasure(tm2);
for (int j = 0; j < parameterSignature.length; j++) {
String t1 = tm3.getParameterTypes().get(j).toString();
String t2 = parameterSignature[j];
if (t2.indexOf('.') == -1) {
t1 = t1.substring(t1.lastIndexOf('.') + 1);
}
if (!t1.equals(t2)) {
return false;
}
}
return true;
} else {
return false;
}
};
}
public static TypeElement getElementTypeOf(Element elt) {
ElementKind kind = elt.getKind();
if (kind == ElementKind.CLASS || kind == ElementKind.INTERFACE || kind == ElementKind.ENUM) {
return (TypeElement) elt;
}
Element enclosingElt = elt.getEnclosingElement();
if (enclosingElt != null) {
return getElementTypeOf(enclosingElt);
}
return null;
}
private static final Pattern WHITESPACE_CLUSTER_PATTERN = Pattern.compile("\\s+");
public static String normalizeWhitespaces(String s) {
Matcher matcher = WHITESPACE_CLUSTER_PATTERN.matcher(s);
return matcher.replaceAll(" ").trim();
}
public static Set<DeclaredType> resolveAncestorTypes(TypeElement typeElt, boolean withSuper, boolean withInterfaces) {
Set<DeclaredType> ancestors = new LinkedHashSet<>();
resolveAncestorTypes(typeElt, ancestors, withSuper, withInterfaces);
return ancestors;
}
private static void resolveAncestorTypes(TypeElement typeElt, Set<DeclaredType> ancestors, boolean withSuper, boolean withInterfaces) {
List<TypeMirror> superTypes = new ArrayList<>();
if (withSuper && typeElt.getSuperclass() != null) {
superTypes.add(typeElt.getSuperclass());
}
if (withInterfaces) {
superTypes.addAll(typeElt.getInterfaces());
}
for (TypeMirror superType : superTypes) {
if (superType.getKind() == TypeKind.DECLARED) {
DeclaredType superDeclaredType = (DeclaredType) superType;
if (!ancestors.contains(superDeclaredType)) {
ancestors.add(superDeclaredType);
resolveAncestorTypes((TypeElement) superDeclaredType.asElement(), ancestors, withSuper, withInterfaces);
}
}
}
}
static void checkUnderModule(Model model, String annotation) {
if (model.getModule() == null) {
throw new GenException(model.getElement(), "Declaration annotated with " + annotation + " must be under a package annotated" +
"with @ModuleGen. Check that the package '" + model.getFqn() +
"' or a parent package contains a 'package-info.java' using the @ModuleGen annotation");
}
}
static void ensureParentDir(File f) {
if (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
}
static String toString(TypeMirror mirror) {
StringBuilder buffer = new StringBuilder();
toString(mirror, buffer);
return buffer.toString();
}
static void toString(TypeMirror mirror, StringBuilder buffer) {
switch (mirror.getKind()) {
case DECLARED: {
DeclaredType dt = (DeclaredType) mirror;
TypeElement elt = (TypeElement) dt.asElement();
buffer.append(elt.getQualifiedName().toString());
List<? extends TypeMirror> args = dt.getTypeArguments();
if (args.size() > 0) {
buffer.append("<");
for (int i = 0;i < args.size();i++) {
if (i > 0) {
buffer.append(",");
}
toString(args.get(i), buffer);
}
buffer.append(">");
}
break;
}
case WILDCARD: {
javax.lang.model.type.WildcardType wt = (javax.lang.model.type.WildcardType) mirror;
buffer.append("?");
if (wt.getSuperBound() != null) {
buffer.append(" super ");
toString(wt.getSuperBound(), buffer);
} else if (wt.getExtendsBound() != null) {
buffer.append(" extends ");
toString(wt.getExtendsBound(), buffer);
}
break;
}
case TYPEVAR: {
javax.lang.model.type.TypeVariable tv = (TypeVariable) mirror;
TypeParameterElement elt = (TypeParameterElement) tv.asElement();
buffer.append(elt.getSimpleName().toString());
if (tv.getUpperBound() != null && !tv.getUpperBound().toString().equals("java.lang.Object")) {
buffer.append(" extends ");
toString(tv.getUpperBound(), buffer);
} else if (tv.getLowerBound() != null && tv.getLowerBound().getKind() != TypeKind.NULL) {
buffer.append(" super ");
toString(tv.getUpperBound(), buffer);
}
break;
}
case BYTE:
case SHORT:
case INT:
case LONG:
case FLOAT:
case DOUBLE:
case CHAR:
case BOOLEAN: {
PrimitiveType pm = (PrimitiveType) mirror;
buffer.append(pm.getKind().name().toLowerCase());
break;
}
case ARRAY: {
ArrayType at = (ArrayType) mirror;
toString(at.getComponentType(), buffer);
buffer.append("[]");
break;
}
default:
throw new UnsupportedOperationException("todo " + mirror + " " + mirror.getKind());
}
}
public static Method getReflectMethod(ProcessingEnvironment env, ExecutableElement modelMethod) {
ClassLoader loader = CodeGen.loaderMap.get(env);
if (loader != null) {
return getReflectMethod(loader, modelMethod);
}
return null;
}
public static Method getReflectMethod(ClassLoader loader, ExecutableElement modelMethod) {
TypeElement typeElt = (TypeElement) modelMethod.getEnclosingElement();
Method method = null;
try {
Class<?> clazz = loader.loadClass(typeElt.getQualifiedName().toString());
StringBuilder sb = new StringBuilder(modelMethod.getSimpleName());
sb.append("(");
List<? extends VariableElement> params = modelMethod.getParameters();
for (int i = 0;i < params.size();i++) {
if (i > 0) {
sb.append(",");
}
VariableElement param = params.get(i);
toString(param.asType(), sb);
}
sb.append(")");
String s = sb.toString();
for (Method m : clazz.getMethods()) {
String sign = m.toGenericString();
int pos = sign.indexOf('(');
pos = sign.lastIndexOf('.', pos) + 1;
sign = sign.substring(pos);
sign = sign.replace(", ", ",");
if (sign.equals(s)) {
if (method != null) {
if (method.getReturnType().isAssignableFrom(m.getReturnType())) {
method = m;
}
} else {
method = m;
}
}
}
} catch (ClassNotFoundException e) {
}
return method;
}
public static ClassKind getAnnotatedDataObjectAnnotatedSerializationType(Elements elementUtils, TypeElement dataObjectElt) {
return elementUtils.getAllMembers(dataObjectElt)
.stream()
.flatMap(Helper.FILTER_METHOD)
.filter(exeElt -> exeElt.getParameters().isEmpty() && exeElt.getSimpleName().toString().equals("toJson"))
.flatMap(exeElt -> {
ClassKind ck;
switch (exeElt.getReturnType().toString()) {
case "io.vertx.core.json.JsonObject":
return Stream.of(ClassKind.JSON_OBJECT);
case "java.lang.String":
return Stream.of(ClassKind.STRING);
}
return Stream.empty();
})
.findFirst()
.orElse(null);
}
public static boolean isConcreteClass(TypeElement element) {
return element.getKind() == ElementKind.CLASS && !element.getModifiers().contains(Modifier.ABSTRACT);
}
public static boolean isAbstractClassOrInterface(TypeElement element) {
return element.getKind().isInterface() ||
(element.getKind() == ElementKind.CLASS && element.getModifiers().contains(Modifier.ABSTRACT));
}
private static final Set<String> dataObjectTypes = new HashSet<>(Arrays.asList("io.vertx.core.json.JsonObject", "java.lang.String"));
public static ClassKind getAnnotatedDataObjectDeserialisationType(Elements elementUtils, Types typeUtils, TypeElement dataObjectElt) {
if (isConcreteClass(dataObjectElt)) {
Set<String> types = elementUtils
.getAllMembers(dataObjectElt)
.stream()
.filter(e -> e.getKind() == ElementKind.CONSTRUCTOR)
.map(e -> (ExecutableElement) e)
.filter(constructor ->
constructor.getParameters().size() == 1 &&
constructor.getModifiers().contains(Modifier.PUBLIC) &&
dataObjectTypes.contains(constructor.getParameters().get(0).asType().toString()))
.map(ctor -> ctor.getParameters().get(0).asType().toString())
.collect(Collectors.toSet());
if (types.contains("io.vertx.core.json.JsonObject")) {
return ClassKind.JSON_OBJECT;
} else if (types.contains("java.lang.String")) {
return ClassKind.STRING;
}
}
return null;
}
public static boolean isGenIgnore(Element elt) {
return elt.getAnnotation(GenIgnore.class) != null;
}
}