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

Interface-based config support #412

Draft
wants to merge 36 commits into
base: trunk
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f24998d
Initial work to support interface-based configs
Tim203 Jul 2, 2023
efcb213
Allow all tests to run properly
Tim203 Jul 3, 2023
b29d241
Split some classes and added Range annotations
Tim203 Jan 3, 2024
e4b7f7f
Added default value annotations and restructured tests
Tim203 Jan 3, 2024
948cebe
Allow setter return type to be non-void
Tim203 Jan 3, 2024
ba11369
Auto-generate simple mappings
Tim203 Jan 3, 2024
7f6059f
Added Hidden annotation and added Processor.AdvancedFactory to aid it
Tim203 Jan 5, 2024
78bcaf5
Added support for some build-in annotations and added another addProc…
Tim203 Jan 6, 2024
c8524c8
Started working on adding tests for interfaces runtime
Tim203 Jan 6, 2024
336de1c
Merge remote-tracking branch 'origin/master' into feature/interfaces
Tim203 Jan 6, 2024
6f23846
Use correct impl name for mappings
Tim203 Jan 6, 2024
f07dc2f
chore(build): Only set test flags on newer JDK versions
zml2008 Jan 7, 2024
6c7f27c
Add all annotations that support fields. Use messager for errors
Tim203 Jan 13, 2024
c009449
Made AnnotationDefaults easier to follow
Tim203 Jan 13, 2024
a9c0e2f
Added support for default getters and default setters
Tim203 Jan 14, 2024
e0d9d42
Notify users about Hidden limitation. Optimized Hidden constraint
Tim203 Jan 14, 2024
224e87c
Exit gracefully on failure
Tim203 Jan 14, 2024
b62cab5
Add support for Gradle incremental annotation processing
Tim203 Feb 1, 2024
2d89b9d
Added Field annotation
Tim203 Feb 11, 2024
40d1c07
Apply spotless
Tim203 Feb 11, 2024
ab13924
Applied forbiddenApi fixes
Tim203 Feb 11, 2024
c5533d5
Renamed error to printError to trick PMD
Tim203 Feb 11, 2024
e9c0dfc
spotlessApply
Tim203 Feb 11, 2024
ebed0c5
Fix pmdTest
Tim203 Feb 11, 2024
27786f0
Set core as api dependency
Tim203 Feb 12, 2024
2add4c9
Use superinterface instead of enclosed element
Tim203 Feb 12, 2024
c2dfc96
Set a default value for config sections
Tim203 Feb 12, 2024
1af436d
Update test
Tim203 Feb 12, 2024
7e45e31
Added serialization to InterfaceTypeSerializer
Tim203 Feb 13, 2024
6945a5d
Respect superclasses' declaration of Exclude
Camotoy May 26, 2024
edd0685
Friendly error if implementation name cannot be found
Camotoy May 26, 2024
4347700
Made it easier to use the interface's default options
Tim203 Jun 12, 2024
e79e4d8
Oops, it's the other way around woo
Tim203 Jun 12, 2024
31c63fd
Superclasses with ConfigSerializable define order
Camotoy Aug 21, 2024
7543cd4
Don't try to initialize ConfigSerializable if @Field is marked
Camotoy Aug 24, 2024
7b4769c
InterfaceDefaultOptions#addTo with ObjectMapper modification
Camotoy Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added support for default getters and default setters
Tim203 committed Jan 14, 2024
commit a9c0e2fb2daf31839200111e2dfe68aea5a2d1c6
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

import com.google.auto.common.MoreTypes;
import com.squareup.javapoet.AnnotationSpec;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -34,6 +35,15 @@ public void process(
final TypeMirror nodeType,
final FieldSpecBuilderTracker fieldSpec
) throws IllegalStateException {
// there are two types of default values, one using annotations and one using the default value of a default method

// first, handle default value of a default method getter
if (element.isDefault() && element.getParameters().isEmpty() && hasNoAnnotationDefaults(element)) {
fieldSpec.initializer("$T.super.$L()", element.getEnclosingElement(), element.getSimpleName());
return;
}

// if it's not using the default value of a default method, use the annotations
final @Nullable DefaultBoolean defaultBoolean = annotation(element, DefaultBoolean.class);
final @Nullable DefaultDecimal defaultDecimal = annotation(element, DefaultDecimal.class);
final @Nullable DefaultNumeric defaultNumeric = annotation(element, DefaultNumeric.class);
@@ -94,4 +104,13 @@ public void process(
fieldSpec.initializer(isString ? "$S" : "$L", value);
}

static boolean hasNoAnnotationDefaults(final AnnotatedConstruct construct) {
for (Class<? extends Annotation> defaultAnnotation : INSTANCE.processes()) {
if (annotation(construct, defaultAnnotation) != null) {
return false;
}
}
return true;
}

}
Original file line number Diff line number Diff line change
@@ -110,38 +110,13 @@ private void gatherElementSpec(
if (parameters.size() == 1) {
// setter
final VariableElement parameter = parameters.get(0);

final MethodSpec.Builder method = MethodSpec.overriding(element)
.addStatement(
"this.$N = $N",
element.getSimpleName(),
parameter.getSimpleName()
);

// if it's not void
if (!MoreTypes.isTypeOf(Void.TYPE, nodeType)) {
// the return type can be a parent type of parameter, but it has to be assignable
if (!this.processor.typeUtils.isAssignable(parameter.asType(), nodeType)) {
this.processor.error(
"Cannot create a setter with return type %s for argument type %s. Method: %s",
nodeType,
parameter.asType(),
element
);
continue;
}
method.addStatement("return this.$N", element.getSimpleName());
final boolean success = handleSetter(element, simpleName, parameter, nodeType, spec);
if (!success) {
continue;
}

spec.add(simpleName + "#" + parameter.getSimpleName().toString(), method);
nodeType = parameter.asType();
} else {
// getter
spec.add(
simpleName,
MethodSpec.overriding(element)
.addStatement("return $N", element.getSimpleName())
);
handleGetter(element, simpleName, nodeType, spec);
}

final FieldSpec.Builder fieldSpec = FieldSpec.builder(TypeName.get(nodeType), simpleName, Modifier.PRIVATE);
@@ -158,4 +133,71 @@ private void gatherElementSpec(
}
}

private boolean handleSetter(
final ExecutableElement element,
final String simpleName,
final VariableElement parameter,
final TypeMirror returnType,
final TypeSpecBuilderTracker spec
) {
final MethodSpec.Builder method = MethodSpec.overriding(element);

// we have two main branches of setters, default non-void setters and non-default any setters
if (element.isDefault()) {
if (MoreTypes.isTypeOf(Void.TYPE, returnType)) {
this.processor.error("A default setter cannot have void as return type. Method: " + element);
return false;
}

method.addStatement(
"this.$N = $T.super.$L($N)",
simpleName,
element.getEnclosingElement(),
simpleName,
parameter.getSimpleName()
);
} else {
method.addStatement("this.$N = $N", simpleName, parameter.getSimpleName());
}

// if it's not void
if (!MoreTypes.isTypeOf(Void.TYPE, returnType)) {
// the return type can be a parent type of parameter, but it has to be assignable
if (!this.processor.typeUtils.isAssignable(parameter.asType(), returnType)) {
this.processor.error(
"Cannot create a setter with return type %s for argument type %s. Method: %s",
returnType,
parameter.asType(),
element
);
return false;
}
method.addStatement("return this.$N", simpleName);
}

spec.add(simpleName + "#" + parameter.getSimpleName(), method);
return true;
}

private void handleGetter(
final ExecutableElement element,
final String simpleName,
final TypeMirror nodeType,
final TypeSpecBuilderTracker spec
) {
// voids aren't valid
if (MoreTypes.isTypeOf(Void.TYPE, nodeType)) {
this.processor.error(
"Cannot create a getter with return type void for method %s, did you forget to @Exclude this method?",
element
);
}

spec.add(
simpleName,
MethodSpec.overriding(element)
.addStatement("return $N", element.getSimpleName())
);
}

}
Original file line number Diff line number Diff line change
@@ -64,4 +64,15 @@ public interface CorrectDefaults {

@DefaultString("Hello world!")
String strawberry();

@DefaultString("Hi")
void tamarillo(String value);

default String ugli() {
return "A fruit";
}

default int velvetApple() {
return 500;
}
}
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@
* Automatically generated implementation of the config */
@ConfigSerializable
final class CorrectDefaultsImpl implements CorrectDefaults {

@DefaultBoolean(false)
private boolean apple = false;

@@ -70,6 +69,13 @@ final class CorrectDefaultsImpl implements CorrectDefaults {
@DefaultString("Hello world!")
private String strawberry = "Hello world!";

@DefaultString("Hi")
private String tamarillo = "Hi";

private String ugli = CorrectDefaults.super.ugli();

private int velvetApple = CorrectDefaults.super.velvetApple();

@Override
public boolean apple() {
return apple;
@@ -164,4 +170,19 @@ public String raspberry() {
public String strawberry() {
return strawberry;
}

@Override
public void tamarillo(String value) {
this.tamarillo = value;
}

@Override
public String ugli() {
return ugli;
}

@Override
public int velvetApple() {
return velvetApple;
}
}
Original file line number Diff line number Diff line change
@@ -9,4 +9,8 @@ public interface BasicConfig {
void hi(String value);

String hello(String value);

default String hey(String value) {
return "Hello";
}
}
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@ final class BasicConfigImpl implements BasicConfig {

private String hi;

private String hey;

@Override
public String hello() {
return hello;
@@ -27,4 +29,10 @@ public String hello(String value) {
this.hello = value;
return this.hello;
}

@Override
public String hey(String value) {
this.hey = BasicConfig.super.hey(value);
return this.hey;
}
}