Skip to content

Commit 03984fb

Browse files
authored
Support enums (#14)
1 parent a29a152 commit 03984fb

File tree

14 files changed

+406
-30
lines changed

14 files changed

+406
-30
lines changed

Diff for: sourcegen-generator-java/src/main/java/io/micronaut/sourcegen/JavaPoetSourceGenerator.java

+36-24
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
*/
1616
package io.micronaut.sourcegen;
1717

18+
import io.micronaut.core.annotation.Internal;
19+
import io.micronaut.core.annotation.Nullable;
20+
import io.micronaut.core.naming.NameUtils;
21+
import io.micronaut.inject.visitor.VisitorContext;
22+
import io.micronaut.sourcegen.generator.SourceGenerator;
1823
import io.micronaut.sourcegen.javapoet.AnnotationSpec;
1924
import io.micronaut.sourcegen.javapoet.ClassName;
2025
import io.micronaut.sourcegen.javapoet.FieldSpec;
@@ -26,14 +31,10 @@
2631
import io.micronaut.sourcegen.javapoet.TypeSpec;
2732
import io.micronaut.sourcegen.javapoet.TypeVariableName;
2833
import io.micronaut.sourcegen.javapoet.WildcardTypeName;
29-
import io.micronaut.core.annotation.Internal;
30-
import io.micronaut.core.annotation.Nullable;
31-
import io.micronaut.core.naming.NameUtils;
32-
import io.micronaut.inject.visitor.VisitorContext;
33-
import io.micronaut.sourcegen.generator.SourceGenerator;
3434
import io.micronaut.sourcegen.model.AnnotationDef;
3535
import io.micronaut.sourcegen.model.ClassDef;
3636
import io.micronaut.sourcegen.model.ClassTypeDef;
37+
import io.micronaut.sourcegen.model.EnumDef;
3738
import io.micronaut.sourcegen.model.ExpressionDef;
3839
import io.micronaut.sourcegen.model.FieldDef;
3940
import io.micronaut.sourcegen.model.InterfaceDef;
@@ -51,8 +52,6 @@
5152
import java.util.Map;
5253
import java.util.stream.Collectors;
5354

54-
import static java.lang.Character.isISOControl;
55-
5655
/**
5756
* The Java source generator.
5857
*
@@ -75,6 +74,8 @@ public void write(ObjectDef objectDef, Writer writer) throws IOException {
7574
writeRecord(writer, recordDef);
7675
} else if (objectDef instanceof InterfaceDef interfaceDef) {
7776
writeInterface(writer, interfaceDef);
77+
} else if (objectDef instanceof EnumDef enumDef) {
78+
writeEnum(writer, enumDef);
7879
} else {
7980
throw new IllegalStateException("Unknown object definition: " + objectDef);
8081
}
@@ -123,6 +124,24 @@ private void writeInterface(Writer writer, InterfaceDef interfaceDef) throws IOE
123124
javaFile.writeTo(writer);
124125
}
125126

127+
private void writeEnum(Writer writer, EnumDef enumDef) throws IOException {
128+
TypeSpec.Builder enumBuilder = TypeSpec.enumBuilder(enumDef.getSimpleName());
129+
enumBuilder.addModifiers(enumDef.getModifiersArray());
130+
enumDef.getSuperinterfaces().stream().map(this::asType).forEach(enumBuilder::addSuperinterface);
131+
132+
for (String enumConstant : enumDef.getEnumConstants()) {
133+
enumBuilder.addEnumConstant(enumConstant);
134+
}
135+
136+
for (MethodDef method : enumDef.getMethods()) {
137+
enumBuilder.addMethod(
138+
asMethodSpec(enumDef, method)
139+
);
140+
}
141+
JavaFile javaFile = JavaFile.builder(enumDef.getPackageName(), enumBuilder.build()).build();
142+
javaFile.writeTo(writer);
143+
}
144+
126145
private void writeClass(Writer writer, ClassDef classDef) throws IOException {
127146
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(classDef.getSimpleName());
128147
classBuilder.addModifiers(classDef.getModifiersArray());
@@ -255,30 +274,14 @@ private AnnotationSpec asAnnotationSpec(AnnotationDef annotationDef) {
255274
} else if (value instanceof Float) {
256275
builder = builder.addMember(memberName, "$Lf", value);
257276
} else if (value instanceof Character) {
258-
builder = builder.addMember(memberName, "'$L'", characterLiteralWithoutSingleQuotes((char) value));
277+
builder = builder.addMember(memberName, "'$L'", io.micronaut.sourcegen.javapoet.Util.characterLiteralWithoutSingleQuotes((char) value));
259278
} else {
260279
builder = builder.addMember(memberName, "$L", value);
261280
}
262281
}
263282
return builder.build();
264283
}
265284

266-
// Copy from io.micronaut.javapoet.Util
267-
private static String characterLiteralWithoutSingleQuotes(char c) {
268-
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
269-
return switch (c) {
270-
case '\b' -> "\\b"; /* \u0008: backspace (BS) */
271-
case '\t' -> "\\t"; /* \u0009: horizontal tab (HT) */
272-
case '\n' -> "\\n"; /* \u000a: linefeed (LF) */
273-
case '\f' -> "\\f"; /* \u000c: form feed (FF) */
274-
case '\r' -> "\\r"; /* \u000d: carriage return (CR) */
275-
case '\"' -> "\""; /* \u0022: double quote (") */
276-
case '\'' -> "\\'"; /* \u0027: single quote (') */
277-
case '\\' -> "\\\\"; /* \u005c: backslash (\) */
278-
default -> isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
279-
};
280-
}
281-
282285
private TypeName asType(TypeDef typeDef) {
283286
if (typeDef instanceof ClassTypeDef.Parameterized parameterized) {
284287
return ParameterizedTypeName.get(
@@ -351,6 +354,15 @@ private static String renderExpression(@Nullable ObjectDef objectDef, MethodDef
351354
if (expressionDef instanceof ExpressionDef.Convert convertExpressionDef) {
352355
return renderVariable(objectDef, methodDef, convertExpressionDef.variable());
353356
}
357+
if (expressionDef instanceof ExpressionDef.CallInstanceMethod callInstanceMethod) {
358+
return renderVariable(objectDef, methodDef, callInstanceMethod.instance())
359+
+ "." + callInstanceMethod.name()
360+
+ "(" + callInstanceMethod.parameters()
361+
.stream()
362+
.map(exp -> renderExpression(objectDef, methodDef, expressionDef))
363+
.collect(Collectors.joining(", "))
364+
+ ")";
365+
}
354366
if (expressionDef instanceof VariableDef variableDef) {
355367
return renderVariable(objectDef, methodDef, variableDef);
356368
}

Diff for: sourcegen-generator-java/src/main/java/io/micronaut/sourcegen/javapoet/Util.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* Like Guava, but worse and standalone. This makes it easier to mix JavaPoet with libraries that
3333
* bring their own version of Guava.
3434
*/
35-
final class Util {
35+
public final class Util {
3636
private Util() {
3737
}
3838

@@ -86,7 +86,7 @@ static void requireExactlyOneOf(Set<Modifier> modifiers, Modifier... mutuallyExc
8686
modifiers, Arrays.toString(mutuallyExclusive));
8787
}
8888

89-
static String characterLiteralWithoutSingleQuotes(char c) {
89+
public static String characterLiteralWithoutSingleQuotes(char c) {
9090
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
9191
switch (c) {
9292
case '\b': return "\\b"; /* \u0008: backspace (BS) */

Diff for: sourcegen-generator-kotlin/src/main/java/io/micronaut/sourcegen/KotlinPoetSourceGenerator.java

+56
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.micronaut.sourcegen.model.AnnotationDef;
3737
import io.micronaut.sourcegen.model.ClassDef;
3838
import io.micronaut.sourcegen.model.ClassTypeDef;
39+
import io.micronaut.sourcegen.model.EnumDef;
3940
import io.micronaut.sourcegen.model.ExpressionDef;
4041
import io.micronaut.sourcegen.model.FieldDef;
4142
import io.micronaut.sourcegen.model.InterfaceDef;
@@ -84,6 +85,8 @@ public void write(ObjectDef objectDef, Writer writer) throws IOException {
8485
writeRecordDef(writer, recordDef);
8586
} else if (objectDef instanceof InterfaceDef interfaceDef) {
8687
writeInterface(writer, interfaceDef);
88+
} else if (objectDef instanceof EnumDef enumDef) {
89+
writeEnumDef(writer, enumDef);
8790
} else {
8891
throw new IllegalStateException("Unknown object definition: " + objectDef);
8992
}
@@ -273,6 +276,41 @@ private void writeRecordDef(Writer writer, RecordDef recordDef) throws IOExcepti
273276
.writeTo(writer);
274277
}
275278

279+
private void writeEnumDef(Writer writer, EnumDef enumDef) throws IOException {
280+
TypeSpec.Builder enumBuilder = TypeSpec.enumBuilder(enumDef.getSimpleName());
281+
enumBuilder.addModifiers(asKModifiers(enumDef.getModifiers()));
282+
enumDef.getSuperinterfaces().stream().map(this::asType).forEach(it -> enumBuilder.addSuperinterface(it, CodeBlock.Companion.getEMPTY$kotlinpoet()));
283+
284+
for (String enumConstant : enumDef.getEnumConstants()) {
285+
enumBuilder.addEnumConstant(enumConstant);
286+
}
287+
288+
TypeSpec.Builder companionBuilder = null;
289+
for (MethodDef method : enumDef.getMethods()) {
290+
Set<Modifier> modifiers = method.getModifiers();
291+
if (modifiers.contains(Modifier.STATIC)) {
292+
if (companionBuilder == null) {
293+
companionBuilder = TypeSpec.companionObjectBuilder();
294+
}
295+
modifiers = stripStatic(modifiers);
296+
companionBuilder.addFunction(
297+
buildFunction(null, method, modifiers)
298+
);
299+
} else {
300+
enumBuilder.addFunction(
301+
buildFunction(enumDef, method, modifiers)
302+
);
303+
}
304+
}
305+
if (companionBuilder != null) {
306+
enumBuilder.addType(companionBuilder.build());
307+
}
308+
FileSpec.builder(enumDef.getPackageName(), enumDef.getSimpleName() + ".kt")
309+
.addType(enumBuilder.build())
310+
.build()
311+
.writeTo(writer);
312+
}
313+
276314
private PropertySpec buildNullableProperty(String name,
277315
TypeDef typeDef,
278316
Set<Modifier> modifiers,
@@ -470,6 +508,24 @@ private static ExpResult renderExpression(@Nullable ObjectDef objectDef, MethodD
470508
newInstance.type()
471509
);
472510
}
511+
if (expressionDef instanceof ExpressionDef.CallInstanceMethod callInstanceMethod) {
512+
ExpResult expResult = renderVariable(objectDef, methodDef, callInstanceMethod.instance());
513+
514+
return new ExpResult(
515+
expResult.rendered
516+
+ "." + callInstanceMethod.name()
517+
+ "(" + callInstanceMethod.parameters()
518+
.stream()
519+
.map(exp -> {
520+
ExpResult paramExp = renderExpression(objectDef, methodDef, expressionDef);
521+
return paramExp.rendered;
522+
523+
})
524+
.collect(Collectors.joining(", "))
525+
+ ")",
526+
callInstanceMethod.type()
527+
);
528+
}
473529
if (expressionDef instanceof ExpressionDef.Convert convertExpressionDef) {
474530
ExpResult expResult = renderVariable(objectDef, methodDef, convertExpressionDef.variable());
475531
TypeDef resultType = convertExpressionDef.type();

Diff for: sourcegen-model/src/main/java/io/micronaut/sourcegen/model/AbstractElement.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* @since 1.0
3030
*/
3131
@Experimental
32-
abstract sealed class AbstractElement permits ClassDef, FieldDef, InterfaceDef, MethodDef, ParameterDef, PropertyDef, RecordDef {
32+
abstract sealed class AbstractElement permits ClassDef, EnumDef, FieldDef, InterfaceDef, MethodDef, ParameterDef, PropertyDef, RecordDef {
3333

3434
protected final String name;
3535
protected final Set<Modifier> modifiers;

Diff for: sourcegen-model/src/main/java/io/micronaut/sourcegen/model/AbstractElementBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* @since 1.0
3232
*/
3333
@Experimental
34-
public sealed class AbstractElementBuilder<ThisType> permits ClassDef.ClassDefBuilder, FieldDef.FieldDefBuilder, InterfaceDef.InterfaceDefBuilder, MethodDef.MethodDefBuilder, ParameterDef.ParameterDefBuilder, PropertyDef.PropertyDefBuilder, RecordDef.RecordDefBuilder {
34+
public sealed class AbstractElementBuilder<ThisType> permits ClassDef.ClassDefBuilder, EnumDef.EnumDefBuilder, FieldDef.FieldDefBuilder, InterfaceDef.InterfaceDefBuilder, MethodDef.MethodDefBuilder, ParameterDef.ParameterDefBuilder, PropertyDef.PropertyDefBuilder, RecordDef.RecordDefBuilder {
3535

3636
protected final String name;
3737
protected EnumSet<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2017-2023 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.sourcegen.model;
17+
18+
import io.micronaut.core.annotation.Experimental;
19+
20+
import javax.lang.model.element.Modifier;
21+
import java.util.ArrayList;
22+
import java.util.EnumSet;
23+
import java.util.List;
24+
25+
/**
26+
* The enum definition.
27+
*
28+
* @author Denis Stepanov
29+
* @since 1.0
30+
*/
31+
@Experimental
32+
public final class EnumDef extends AbstractElement implements ObjectDef {
33+
34+
private final List<String> enumConstants;
35+
private final List<MethodDef> methods;
36+
private final List<TypeDef> superinterfaces;
37+
38+
private EnumDef(String name,
39+
EnumSet<Modifier> modifiers,
40+
List<MethodDef> methods,
41+
List<AnnotationDef> annotations,
42+
List<String> enumConstants,
43+
List<TypeDef> superinterfaces) {
44+
super(name, modifiers, annotations);
45+
this.methods = methods;
46+
this.enumConstants = enumConstants;
47+
this.superinterfaces = superinterfaces;
48+
}
49+
50+
public static EnumDefBuilder builder(String name) {
51+
return new EnumDefBuilder(name);
52+
}
53+
54+
public List<MethodDef> getMethods() {
55+
return methods;
56+
}
57+
58+
public List<String> getEnumConstants() {
59+
return enumConstants;
60+
}
61+
62+
public List<TypeDef> getSuperinterfaces() {
63+
return superinterfaces;
64+
}
65+
66+
/**
67+
* The enum definition builder.
68+
*
69+
* @author Denis Stepanov
70+
* @since 1.0
71+
*/
72+
@Experimental
73+
public static final class EnumDefBuilder extends AbstractElementBuilder<EnumDefBuilder> {
74+
75+
private final List<String> enumConstants = new ArrayList<>();
76+
private final List<MethodDef> methods = new ArrayList<>();
77+
private final List<TypeDef> superinterfaces = new ArrayList<>();
78+
79+
private EnumDefBuilder(String name) {
80+
super(name);
81+
}
82+
83+
public EnumDefBuilder addMethod(MethodDef method) {
84+
methods.add(method);
85+
return this;
86+
}
87+
88+
public EnumDefBuilder addEnumConstant(String name) {
89+
enumConstants.add(name);
90+
return this;
91+
}
92+
93+
public EnumDefBuilder addSuperinterface(TypeDef superinterface) {
94+
superinterfaces.add(superinterface);
95+
return this;
96+
}
97+
98+
public EnumDef build() {
99+
return new EnumDef(name, modifiers, methods, annotations, enumConstants, superinterfaces);
100+
}
101+
102+
}
103+
104+
}

Diff for: sourcegen-model/src/main/java/io/micronaut/sourcegen/model/ExpressionDef.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @since 1.0
2727
*/
2828
@Experimental
29-
public sealed interface ExpressionDef permits ExpressionDef.Convert, ExpressionDef.NewInstance, VariableDef {
29+
public sealed interface ExpressionDef permits ExpressionDef.CallInstanceMethod, ExpressionDef.Convert, ExpressionDef.NewInstance, VariableDef {
3030

3131
/**
3232
* The type of the expression.
@@ -51,7 +51,7 @@ record NewInstance(ClassTypeDef type,
5151
/**
5252
* The convert variable expression. (To support Kotlin's nullable -> not-null conversion)
5353
*
54-
* @param type The type
54+
* @param type The type
5555
* @param variable The variable reference
5656
* @author Denis Stepanov
5757
* @since 1.0
@@ -61,4 +61,22 @@ record Convert(TypeDef type,
6161
VariableDef variable) implements ExpressionDef {
6262
}
6363

64+
/**
65+
* The call method expression.
66+
*
67+
* @param instance The instance
68+
* @param name The method name
69+
* @param parameters The parameters
70+
* @param returning The returning
71+
* @author Denis Stepanov
72+
* @since 1.0
73+
*/
74+
@Experimental
75+
record CallInstanceMethod(VariableDef instance, String name, List<ExpressionDef> parameters,
76+
TypeDef returning) implements ExpressionDef {
77+
@Override
78+
public TypeDef type() {
79+
return returning;
80+
}
81+
}
6482
}

0 commit comments

Comments
 (0)