Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how this generates/updates EEAs - from what? #268

Closed
vorburger opened this issue Aug 25, 2024 · 4 comments
Closed

Document how this generates/updates EEAs - from what? #268

vorburger opened this issue Aug 25, 2024 · 4 comments

Comments

@vorburger
Copy link

@sebthom 👋 Hi!

I've (finally, never had any time before) had a slightly closer look at this repo only just today. The README says:

update/regenerate all EEA files

and

Creation of this project was inspired by https://github.com/lastnpe/eclipse-null-eea-augments/ but a different approach in generating/packaging/validating of EEA files/archives has been taken.

First of all, Thank You for the ACK.

I was trying to better understand, and then perhaps contribute a PR here to share that understanding, of how this actually works... but I haven't really figured out yet what this reads as input source to generate EEAs from? (And I admit that I haven't looked at its source code to just figure it out.)

@sebthom
Copy link
Member

sebthom commented Aug 25, 2024

The EEA Generator can scan classes using ClassGraph and output EEA files containing signatures of all methods and fields. It contains some heuristic to mark fields nullable/non-null either based on different annotations (see

private static final Set<String> NULLABLE_ANNOTATIONS = Set.of( //
"android.annotation.Nullable", //
"android.support.annotation.Nullable", //
"androidx.annotation.Nullable", //
"com.mongodb.lang.Nullable", //
"com.sun.istack.internal.Nullable", //
"edu.umd.cs.findbugs.annotations.Nullable", //
"io.reactivex.annotations.Nullable", //
"io.reactivex.rxjava3.annotations.Nullable", //
"io.smallrye.common.constraint.Nullable", //
"io.vertx.codegen.annotations.Nullable", //
"jakarta.annotation.CheckForNull", //
"jakarta.annotation.Nullable", //
"javax.annotation.CheckForNull", //
"javax.annotation.Nullable", //
"net.bytebuddy.utility.nullability.AlwaysNull", //
"net.bytebuddy.utility.nullability.MaybeNull", //
"org.checkerframework.checker.nullness.compatqual.NullableDecl", //
"org.checkerframework.checker.nullness.compatqual.NullableType", //
"org.checkerframework.checker.nullness.qual.Nullable", //
"org.eclipse.jdt.annotation.Nullable", //
"org.eclipse.sisu.Nullable", //
"org.jetbrains.annotations.Nullable", //
"org.jmlspecs.annotation.Nullable", //
"org.netbeans.api.annotations.common.CheckForNull", //
"org.netbeans.api.annotations.common.NullAllowed", //
"org.netbeans.api.annotations.common.NullUnknown", //
"org.springframework.lang.Nullable", //
"org.sonatype.inject.Nullable", //
"org.wildfly.common.annotation.Nullable", //
"reactor.util.annotation.Nullable");
private static final Set<String> NONNULL_ANNOTATIONS = Set.of( //
"android.annotation.NonNull", //
"android.support.annotation.NonNull", //
"androidx.annotation.NonNull", //
"com.sun.istack.internal.NotNull", //
"com.mongodb.lang.NonNull", //
"edu.umd.cs.findbugs.annotations.NonNull", //
"io.reactivex.annotations.NonNull", //
"io.reactivex.rxjava3.annotations.NonNull", //
"javax.annotation.Nonnull", //
"javax.validation.constraints.NotNull", //
"jakarta.annotation.Nonnull", //
"jakarta.validation.constraints.NotNull", //
"lombok.NonNull", //
"net.bytebuddy.utility.nullability.NeverNull", //
"org.checkerframework.checker.nullness.compatqual.NonNullDecl", //
"org.checkerframework.checker.nullness.compatqual.NonNullType", //
"org.checkerframework.checker.nullness.qual.NonNull", //
"org.eclipse.jdt.annotation.NonNull", //
"org.jetbrains.annotations.NotNull", //
"org.jmlspecs.annotation.NonNull", //
"org.netbeans.api.annotations.common.NonNull", //
"org.springframework.lang.NonNull", //
"org.wildfly.common.annotation.NotNull", //
"reactor.util.annotation.NonNull");
) or if methods/fields follow certain well known conventions ( see
protected static ValueWithComment computeAnnotatedSignature(final EEAFile.ClassMember member, final ClassInfo classInfo,
final ClassMemberInfo memberInfo) {
final var templates = new ArrayList<EEAFile>();
if (isThrowable(classInfo)) {
templates.add(TEMPLATE_THROWABLE);
}
templates.add(TEMPLATE_EXTERNALIZABLE);
templates.add(TEMPLATE_SERIALIZABLE);
templates.add(TEMPLATE_OBJECT);
for (final EEAFile template : templates) {
final var matchingMember = template.findMatchingClassMember(member);
if (matchingMember != null && matchingMember.hasNullAnnotations())
return matchingMember.annotatedSignature;
}
// analyzing a method
if (memberInfo instanceof MethodInfo) {
final MethodInfo methodInfo = (MethodInfo) memberInfo;
/*
* mark the return value of builder methods as @NonNull.
*/
if (classInfo.getName().endsWith("Builder") //
&& !methodInfo.isStatic() // non-static
&& methodInfo.isPublic() //
&& methodInfo.getTypeDescriptor().getResultType() instanceof ClassRefTypeSignature //
&& (methodInfo.getName().equals("build") && methodInfo.getParameterInfo().length == 0 //
|| Objects.equals(((ClassRefTypeSignature) methodInfo.getTypeDescriptor().getResultType()).getClassInfo(), classInfo)))
// (...)Lcom/example/MyBuilder -> (...)L1com/example/MyBuilder;
return new ValueWithComment(insert(member.originalSignature.value, member.originalSignature.value.lastIndexOf(")") + 2, "1"),
"");
/*
* mark the parameter of Comparable#compareTo(Object) as @NonNull.
*/
if (classInfo.implementsInterface("java.lang.Comparable") //
&& !methodInfo.isStatic() // non-static
&& member.originalSignature.value.endsWith(")I") // returns Integer
&& methodInfo.isPublic() //
&& methodInfo.getParameterInfo().length == 1 // only 1 parameter
&& methodInfo.getParameterInfo()[0].getTypeDescriptor() instanceof ClassRefTypeSignature)
// (Lcom/example/Entity;)I -> (L1com/example/Entity;)I
return new ValueWithComment(insert(member.originalSignature.value, 2, "1"), "");
/*
* mark the parameter of single-parameter void methods as @NonNull,
* if the class name matches "*Listener" and the parameter type name matches "*Event"
*/
if (classInfo.isInterface() //
&& classInfo.getName().endsWith("Listener") //
&& !methodInfo.isStatic() // non-static
&& member.originalSignature.value.endsWith(")V") // returns void
&& methodInfo.getParameterInfo().length == 1 // only 1 parameter
&& methodInfo.getParameterInfo()[0].getTypeDescriptor().toString().endsWith("Event"))
// (Ljava/lang/String;)V -> (L1java/lang/String;)V
return new ValueWithComment(insert(member.originalSignature.value, 2, "1"), "");
/*
* mark the parameter of single-parameter methods as @NonNull
* with signature matching: void (add|remove)*Listener(*Listener)
*/
if (!methodInfo.isStatic() // non-static
&& (methodInfo.getName().startsWith("add") || methodInfo.getName().startsWith("remove")) //
&& methodInfo.getName().endsWith("Listener") //
&& member.originalSignature.value.endsWith(")V") // returns void
&& methodInfo.getParameterInfo().length == 1 // only 1 parameter
&& methodInfo.getParameterInfo()[0].getTypeDescriptor().toString().endsWith("Listener"))
return new ValueWithComment( //
member.originalSignature.value.startsWith("(") //
// (Lcom/example/MyListener;)V -> (L1com/example/MyListener;)V
// (TT;)V -> (T1T;)V
? insert(member.originalSignature.value, 2, "1") //
// <T::Lcom/example/MyListener;>(TT;)V --> <1T::Lcom/example/MyListener;>(TT;)V
: insert(member.originalSignature.value, 1, "1"), //
"");
if (hasObjectReturnType(member)) { // returns non-void
if (hasNullableAnnotation(methodInfo.getAnnotationInfo()))
// ()Ljava/lang/String -> ()L0java/lang/String;
return new ValueWithComment(insert(member.originalSignature.value, member.originalSignature.value.lastIndexOf(")") + 2, "0"),
"");
if (hasNonNullAnnotation(methodInfo.getAnnotationInfo()))
// ()Ljava/lang/String -> ()L1java/lang/String;
return new ValueWithComment(insert(member.originalSignature.value, member.originalSignature.value.lastIndexOf(")") + 2, "1"),
"");
}
}
// analyzing a field
if (memberInfo instanceof FieldInfo) {
final FieldInfo fieldInfo = (FieldInfo) memberInfo;
if (hasNullableAnnotation(fieldInfo.getAnnotationInfo()))
return new ValueWithComment(insert(member.originalSignature.value, 1, "0"));
if (fieldInfo.isStatic() && fieldInfo.isFinal() // if the field is static and final we by default expect it to be non-null
|| hasNonNullAnnotation(fieldInfo.getAnnotationInfo()) //
)
// Ljava/lang/String; -> L1java/lang/String;
return new ValueWithComment(insert(member.originalSignature.value, 1, "1"));
}
return new ValueWithComment(member.originalSignature.value);
}
).

The idea now is that when a library is updated, e.g. classes are added/removed etc. You rerun the Generator which scans the classes and also reads the EEAs that were generated (and potentially modified) before and then re-applies those EEAs before it writes the updated EEA files.

I hope that makes sense :-)

@vorburger
Copy link
Author

vorburger commented Aug 26, 2024

then perhaps contribute a PR here to share that understanding

Or not here anymore, but over on https://github.com/lastnpe/eclipse-null-eea-augments, after lastnpe/eclipse-null-eea-augments#168 is merged...

@vorburger
Copy link
Author

Or not here anymore, but over on https://github.com/lastnpe/eclipse-null-eea-augments, after lastnpe/eclipse-null-eea-augments#168 is merged...

At least partially done in lastnpe/eclipse-null-eea-augments@77a4946 already, it seems.

@sebthom
Copy link
Member

sebthom commented Aug 27, 2024

Let's discuss things in the LastNPE repo.

@sebthom sebthom closed this as completed Aug 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants