Skip to content

Commit

Permalink
feat: add ingoreNested handling for NoFactoryMethod
Browse files Browse the repository at this point in the history
Several fixes in here including some generic handling for inherited
interfaces, along with the parent implementation class having
generics that can be solved by the interface's inference. Some of this
is all now tested with unit tests of the AP running.
  • Loading branch information
gabizou committed Oct 1, 2024
1 parent 7c886aa commit 2947110
Show file tree
Hide file tree
Showing 19 changed files with 1,735 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface NoFactoryMethod {

/**
* Whether to ignore nested subclasses or sub packages when determining
* whether to generate a factory method.
*
* @return Whether to ignore nested subclasses or sub packages
*/
boolean ignoreNested() default false;

}
10 changes: 9 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
alias(libs.plugins.spongeConvention) apply false
alias(libs.plugins.indra.sonatype) apply false
alias(libs.plugins.indra.sonatype)
alias(libs.plugins.errorprone)
alias(libs.plugins.eclipseApt)
id('java-library')
Expand Down Expand Up @@ -75,11 +75,19 @@ allprojects {
minimumToolchain(21)
target(21)
}
configurePublications {
artifactId(project.name.toLowerCase())
}
}

indraSpotlessLicenser {
property("name", rootProject.projectName)
}
// tasks.withType(JavaCompile).configureEach {
// options.forkOptions.jvmArgs = [
// '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005'
// ]
// }
}


Expand Down
1,168 changes: 1,168 additions & 0 deletions gradle/verification-metadata.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.annotation.processing.Generated;
import javax.annotation.processing.Messager;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
Expand Down Expand Up @@ -121,14 +122,6 @@ static boolean generateMethods(final Property property) {
}
}

static boolean useInToString(final Property property) {
final PropertySettings settings = ClassGenerator.getPropertySettings(property);
if (settings != null) {
return settings.useInToString();
}
return !property.getMostSpecificMethod().isDefault();
}

static UseField getUseField(final TypeMirror clazz, final String fieldName) {
if (clazz.getKind() != TypeKind.DECLARED) {
return null;
Expand Down Expand Up @@ -197,13 +190,17 @@ public void setNullPolicy(final NullPolicy nullPolicy) {
this.nullPolicy = Objects.requireNonNull(nullPolicy, "nullPolicy");
}

private Set<String> alwaysQualifiedImports(final TypeElement element) {
private void alwaysQualifiedImports(TypeSpec.Builder classBuilder, final TypeElement element) {
// always qualify the return types of properties in properties
return this.elements.getAllMembers(element)
final Set<String> alwaysQualified = new HashSet<>();
this.elements.getAllMembers(element)
.stream()
.filter(el -> el.getKind().isClass() || el.getKind().isInterface())
.filter(el -> ((TypeElement) el).getNestingKind().isNested())
.map(el -> el.getSimpleName().toString())
.collect(Collectors.toSet());
.forEach(alwaysQualified::add);

classBuilder.alwaysQualify(alwaysQualified.toArray(new String[0]));
}

private boolean hasNullable(final ExecutableElement method) {
Expand Down Expand Up @@ -244,12 +241,22 @@ public boolean contributeField(final ClassContext classWriter, final DeclaredTyp
classWriter.addField(property);
} else if (field.getModifiers().contains(Modifier.PRIVATE)) {
this.messager.printMessage(Diagnostic.Kind.ERROR, "You've annotated the field " + property.getName() + " with @UseField, "
+ "but it's private. This just won't work.", field);
+ "but it's private. This just won't work.", field);
return false;
} else if (!this.types.isSubtype(field.asType(), property.getType())) {
this.messager.printMessage(Diagnostic.Kind.ERROR, String.format("In event %s with parent %s - you've specified field '%s' of type %s"
+ " but the property has the expected type of %s", this.elements.getBinaryName((TypeElement) property.getAccessor().getEnclosingElement()),
parentType, field.getSimpleName(), field.asType(), property.getType()), field);
} else if (!this.types.isSubtype(this.types.erasure(field.asType()),this.types.erasure(property.getType()))) {
// We need to handle generics and if the field is a subtype of the property, then it fits.
if (this.types.isAssignable(this.types.erasure(property.getType()), this.types.erasure(field.asType()))) {
return true;
}
this.messager.printMessage(Diagnostic.Kind.ERROR,
String.format(
"In event %s with parent %s - you've specified field '%s' of type %s" + " but the property has the expected type of %s",
this.elements.getBinaryName((TypeElement) property.getAccessor().getEnclosingElement()),
parentType,
field.getSimpleName(),
field.asType(),
property.getType()),
field);
return false;
}
return true;
Expand All @@ -266,24 +273,24 @@ private MethodSpec generateConstructor(final DeclaredType parentType, final List
final CodeBlock.Builder initializer = CodeBlock.builder();
for (final Property property : requiredProperties) {
builder.addParameter(TypeName.get(property.getType()), property.getName(), Modifier.FINAL);
// Only if we have a null policy:
// if (value == null) throw new NullPointerException(...)
if (this.nullPolicy != NullPolicy.DISABLE_PRECONDITIONS) {
final boolean useNullTest = !property.getType().getKind().isPrimitive()
&& (((this.nullPolicy == NullPolicy.NON_NULL_BY_DEFAULT && !this.hasNullable(property.getAccessor()))
|| (this.nullPolicy == NullPolicy.NULL_BY_DEFAULT && this.hasNonNull(property.getAccessor())))
&& ClassGenerator.isRequired(property));

if (useNullTest) {
initializer.addStatement(
"this.$1L = $2T.requireNonNull($1L, $3S)",
property.getName(),
ClassGenerator.OBJECTS,
"The property '" + property.getName() + "' was not provided!"
);
continue;
}
// Only if we have a null policy:
// if (value == null) throw new NullPointerException(...)
if (this.nullPolicy != NullPolicy.DISABLE_PRECONDITIONS) {
final boolean useNullTest = !property.getType().getKind().isPrimitive()
&& (((this.nullPolicy == NullPolicy.NON_NULL_BY_DEFAULT && !this.hasNullable(property.getAccessor()))
|| (this.nullPolicy == NullPolicy.NULL_BY_DEFAULT && this.hasNonNull(property.getAccessor())))
&& ClassGenerator.isRequired(property));

if (useNullTest) {
initializer.addStatement(
"this.$1L = $2T.requireNonNull($1L, $3S)",
property.getName(),
ClassGenerator.OBJECTS,
"The property '" + property.getName() + "' was not provided!"
);
continue;
}
}

// no null test
initializer.addStatement("this.$1L = $1L", property.getName());
Expand Down Expand Up @@ -339,11 +346,11 @@ private void deriveParentTypeName(TypeSpec.Builder builder, DeclaredType parentT

if (iface instanceof Parameterizable pe) {
pe.getTypeParameters()
.stream()
.map(TypeVariableName::get)
.forEach(classTypeParameters::add);
.stream()
.map(TypeVariableName::get)
.forEach(classTypeParameters::add);
}
final var parentParameterMap = new HashMap<String, TypeName>();

// Inspect both the parent type and the interface we're implementing
// - If the interface we implement has NO type parameters, we can introspect the
// extension for generic types declared on the parent's extension
Expand All @@ -352,9 +359,9 @@ private void deriveParentTypeName(TypeSpec.Builder builder, DeclaredType parentT
// - If the interface does not, populate
if (parentType.asElement() instanceof TypeElement te) {
final var params = te.getTypeParameters()
.stream()
.map(tpe -> TypeName.get(tpe.asType()))
.toArray(TypeName[]::new);
.stream()
.map(tpe -> TypeName.get(tpe.asType()))
.toArray(TypeName[]::new);
final ClassName newClassName = ClassName.get(te);
if (params.length == 0) {
// We won't add type parameters because the interface's type parameters would be added later
Expand All @@ -368,12 +375,18 @@ private void deriveParentTypeName(TypeSpec.Builder builder, DeclaredType parentT
for (TypeMirror extended : iface.getInterfaces()) {
if (extended instanceof DeclaredType de) {
de.getTypeArguments()
.stream()
.map(TypeName::get)
.forEach(innerParams::add);
.stream()
.map(ite -> {
if (ite instanceof DeclaredType dte) {
final var nte = (TypeElement) dte.asElement();
return ClassName.get(nte);
}
return TypeName.get(ite);
})
.forEach(innerParams::add);
}
}
final var modifiedParams = innerParams.toArray(new TypeName[innerParams.size()]);
final var modifiedParams = innerParams.toArray(new TypeName[0]);
superClassName = ParameterizedTypeName.get(newClassName, modifiedParams);
}
builder.superclass(superClassName);
Expand All @@ -383,8 +396,8 @@ private void deriveParentTypeName(TypeSpec.Builder builder, DeclaredType parentT
/**
* Create the event class.
*
* @param type The type
* @param name The canonical of the generated class
* @param type The type
* @param name The canonical of the generated class
* @param parentType The parent type
* @return The class' contents, or {@code null} if an error was reported while generating the class
*/
Expand All @@ -400,7 +413,7 @@ private void deriveParentTypeName(TypeSpec.Builder builder, DeclaredType parentT
Objects.requireNonNull(name, "name");
Objects.requireNonNull(parentType, "parentType");

final TypeName implementedInterface = TypeName.get(type.asType());
TypeName implementedInterface = this.classNameProvider.getImplementingInterfaceName(type);
List<TypeVariableName> classTypeParameters = new ArrayList<>();

final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(name)
Expand All @@ -413,14 +426,15 @@ private void deriveParentTypeName(TypeSpec.Builder builder, DeclaredType parentT
AnnotationSpec.builder(GeneratedEvent.class)
.addMember("source", "$T.class",
implementedInterface instanceof ParameterizedTypeName
? ((ParameterizedTypeName) implementedInterface).rawType
: implementedInterface
? ((ParameterizedTypeName) implementedInterface).rawType
: implementedInterface
)
.addMember("version", "$S", ClassGenerator.class.getPackage().getImplementationVersion())
.build()
);
this.deriveParentTypeName(classBuilder, parentType, type);
classBuilder.alwaysQualifiedNames.addAll(this.alwaysQualifiedImports(type));
this.alwaysQualifiedImports(classBuilder, type);
classBuilder.avoidClashesWithNestedClasses(type);
classBuilder.originatingElements.addAll(data.extraOrigins);

for (final TypeParameterElement param : type.getTypeParameters()) {
Expand Down Expand Up @@ -485,14 +499,7 @@ private boolean generateWithPlugins(
}

AnnotationSpec generatedAnnotation() {
final ClassName clazz;
if (this.targetVersion.compareTo(SourceVersion.RELEASE_8) > 0) {
clazz = ClassName.get("javax.annotation.processing", "Generated");
} else {
clazz = ClassName.get("javax.annotation", "Generated");
}

return AnnotationSpec.builder(clazz)
return AnnotationSpec.builder(Generated.class)
.addMember("value", "$S", EventImplGenProcessor.class.getName())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
package org.spongepowered.eventimplgen.factory;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import org.spongepowered.eventimplgen.processor.EventGenOptions;

import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.ArrayList;
import java.util.List;

/**
* Provide class names for event interface generation.
Expand Down Expand Up @@ -66,4 +70,22 @@ public ClassName getClassName(final TypeElement clazz, final String classifier)
.toString());
}

public TypeName getImplementingInterfaceName(final TypeElement clazz) {
var original = TypeName.get(clazz.asType());
// In the off chance
if (!clazz.getNestingKind().isNested()) {
return original;
}
if (!clazz.getTypeParameters().isEmpty()) {
final List<TypeName> typeArguments = new ArrayList<>();
for (var typeParameter : clazz.getTypeParameters()) {
typeArguments.add(TypeName.get(typeParameter.asType()));
}
final var ifaceName = ClassName.get(clazz);

return ParameterizedTypeName.get(ifaceName, typeArguments.toArray(new TypeName[0]));
}
return ClassName.get(clazz);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
import org.spongepowered.eventgen.annotations.GenerateFactoryMethod;
import org.spongepowered.eventgen.annotations.NoFactoryMethod;

import javax.annotation.processing.Messager;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.tools.Diagnostic;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -36,11 +40,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.processing.Messager;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.tools.Diagnostic;
import java.util.stream.Collectors;

@Singleton
public class EventGenOptions {
Expand All @@ -51,6 +51,8 @@ public class EventGenOptions {

public static final String SORT_PRIORITY_PREFIX = "sortPriorityPrefix"; // default: original
public static final String GROUPING_PREFIXES = "groupingPrefixes"; // <a>:<b>[,<a>:<b>]* default: from:to
public static final String INCLUSIVE_FOLDERS = "eventGenInclusiveFolders"; // default: empty, comma separated list of folders to include
public static final String EXCLUSIVE_FOLDERS = "eventGenExclusiveFolders"; // default: empty, comma separated list of folders to exclude

// these two take fully qualified names to annotations that should include or exclude a certain element from implementation generation
public static final String INCLUSIVE_ANNOTATIONS = "inclusiveAnnotations"; // default: GenerateFactoryMethod
Expand All @@ -74,6 +76,24 @@ public String generatedEventFactory() {
return Objects.requireNonNull(this.options.get(EventGenOptions.GENERATED_EVENT_FACTORY), "invalid state, factory name not provided");
}

public Set<String> inclusivePackages() {
final var rawInclusiveFolders = this.options.get(EventGenOptions.INCLUSIVE_FOLDERS);
if (rawInclusiveFolders == null) {
return Collections.emptySet();
}
final var folders = rawInclusiveFolders.split(",");
return Arrays.stream(folders).map(s -> s.replace("/", ".")).collect(Collectors.toUnmodifiableSet());
}

public Set<String> exclusivePackages() {
final var rawExclusiveFolders = this.options.get(EventGenOptions.EXCLUSIVE_FOLDERS);
if (rawExclusiveFolders == null) {
return Collections.emptySet();
}
final var folders = rawExclusiveFolders.split(",");
return Arrays.stream(folders).map(s -> s.replace("/", ".")).collect(Collectors.toUnmodifiableSet());
}

public @Nullable String sortPriorityPrefix() {
return this.options.getOrDefault(EventGenOptions.SORT_PRIORITY_PREFIX, "original");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
@AutoService(Processor.class)
@SupportedOptions({
EventGenOptions.GENERATED_EVENT_FACTORY,
EventGenOptions.INCLUSIVE_FOLDERS,
EventGenOptions.EXCLUSIVE_FOLDERS,
EventGenOptions.SORT_PRIORITY_PREFIX,
EventGenOptions.GROUPING_PREFIXES,
EventGenOptions.INCLUSIVE_ANNOTATIONS,
Expand Down
Loading

0 comments on commit 2947110

Please sign in to comment.