Skip to content

Commit 6665d7c

Browse files
authored
Handling enum values in a consistent way in code generation. (helidon-io#9167)
Handling type values in a consistent way in code generation.
1 parent d8fc80c commit 6665d7c

7 files changed

Lines changed: 223 additions & 28 deletions

File tree

builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -664,6 +664,15 @@ private static <T extends Enum<T>> T asEnum(TypeName typeName, String property,
664664
if (value instanceof String str) {
665665
return Enum.valueOf(type, str);
666666
}
667+
if (value instanceof EnumValue enumValue) {
668+
if (enumValue.type().equals(TypeName.create(type))) {
669+
return Enum.valueOf(type, enumValue.name());
670+
}
671+
672+
throw new IllegalStateException("Property " + property + " is of enum type for enum "
673+
+ enumValue.type().fqName() + ", yet you requested "
674+
+ type.getName());
675+
}
667676

668677
throw new IllegalArgumentException(typeName.fqName() + " property " + property
669678
+ " of type " + value.getClass().getName()
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2024 Oracle and/or its affiliates.
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+
* http://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+
17+
package io.helidon.common.types;
18+
19+
/**
20+
* When creating an {@link io.helidon.common.types.Annotation}, we may need to create an enum value
21+
* without access to the enumeration.
22+
* <p>
23+
* In such a case, you can use this type when calling {@link io.helidon.common.types.Annotation.Builder#putValue(String, Object)}
24+
*/
25+
public interface EnumValue {
26+
/**
27+
* Create a new enum value, when the enum is not available on classpath.
28+
*
29+
* @param enumType type of the enumeration
30+
* @param enumName value of the enumeration
31+
* @return enum value
32+
*/
33+
static EnumValue create(TypeName enumType, String enumName) {
34+
return new EnumValue() {
35+
@Override
36+
public TypeName type() {
37+
return enumType;
38+
}
39+
40+
@Override
41+
public String name() {
42+
return enumName;
43+
}
44+
};
45+
}
46+
47+
/**
48+
* Type of the enumeration.
49+
*
50+
* @return type name of the enumeration
51+
*/
52+
TypeName type();
53+
54+
/**
55+
* The enum value.
56+
*
57+
* @return enum value
58+
*/
59+
String name();
60+
}

codegen/apt/src/main/java/io/helidon/codegen/apt/ToAnnotationValueVisitor.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import javax.lang.model.type.TypeMirror;
2929
import javax.lang.model.util.Elements;
3030

31+
import io.helidon.common.types.EnumValue;
32+
import io.helidon.common.types.TypeNames;
33+
3134
class ToAnnotationValueVisitor implements AnnotationValueVisitor<Object, Object> {
3235
private final Elements elements;
3336
private boolean mapVoidToNull;
@@ -126,18 +129,30 @@ public Object visitString(String s, Object o) {
126129
return s;
127130
}
128131

132+
@SuppressWarnings("removal")
129133
@Override
130134
public Object visitType(TypeMirror t, Object o) {
131-
String val = t.toString();
132-
if (mapVoidToNull && ("void".equals(val) || Void.class.getName().equals(val))) {
133-
val = null;
135+
var maybeType = AptTypeFactory.createTypeName(t);
136+
if (maybeType.isEmpty()) {
137+
return null;
138+
}
139+
var type = maybeType.get();
140+
if (mapVoidToNull && (type.equals(TypeNames.BOXED_VOID) || type.equals(TypeNames.PRIMITIVE_VOID))) {
141+
return null;
134142
}
135-
return val;
143+
return type;
136144
}
137145

146+
@SuppressWarnings("removal")
138147
@Override
139148
public Object visitEnumConstant(VariableElement c, Object o) {
140-
return String.valueOf(c.getSimpleName());
149+
var maybeType = AptTypeFactory.createTypeName(c.getEnclosingElement());
150+
151+
if (maybeType.isEmpty()) {
152+
// this will be one-way only
153+
return String.valueOf(c.getSimpleName());
154+
}
155+
return EnumValue.create(maybeType.get(), String.valueOf(c.getSimpleName()));
141156
}
142157

143158
@Override

codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Objects;
2020
import java.util.Set;
2121

22+
import io.helidon.common.types.EnumValue;
2223
import io.helidon.common.types.TypeName;
2324

2425
/**
@@ -27,10 +28,12 @@
2728
public final class AnnotationParameter extends CommonComponent {
2829

2930
private final String value;
31+
private final TypeName importedType;
3032

3133
private AnnotationParameter(Builder builder) {
3234
super(builder);
3335
this.value = resolveValueToString(builder.type(), builder.value);
36+
this.importedType = resolveImport(builder.value);
3437
}
3538

3639
/**
@@ -48,6 +51,26 @@ void writeComponent(ModelWriter writer, Set<String> declaredTokens, ImportOrgani
4851
writer.write(name() + " = " + value);
4952
}
5053

54+
@Override
55+
void addImports(ImportOrganizer.Builder imports) {
56+
if (importedType != null) {
57+
imports.addImport(importedType);
58+
}
59+
}
60+
61+
private static TypeName resolveImport(Object value) {
62+
if (value.getClass().isEnum()) {
63+
return TypeName.create(value.getClass());
64+
}
65+
if (value instanceof TypeName tn) {
66+
return tn;
67+
}
68+
if (value instanceof EnumValue ev) {
69+
return ev.type();
70+
}
71+
return null;
72+
}
73+
5174
private static String resolveValueToString(Type type, Object value) {
5275
Class<?> valueClass = value.getClass();
5376
if (valueClass.isEnum()) {
@@ -58,7 +81,9 @@ private static String resolveValueToString(Type type, Object value) {
5881
return "\"" + stringValue + "\"";
5982
}
6083
} else if (value instanceof TypeName typeName) {
61-
return typeName.fqName() + ".class";
84+
return typeName.classNameWithEnclosingNames() + ".class";
85+
} else if (value instanceof EnumValue enumValue) {
86+
return enumValue.type().classNameWithEnclosingNames() + "." + enumValue.name();
6287
}
6388
return value.toString();
6489
}

codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424

2525
import io.helidon.common.types.Annotation;
26+
import io.helidon.common.types.EnumValue;
2627
import io.helidon.common.types.TypeName;
2728

2829
import io.github.classgraph.AnnotationClassRef;
@@ -85,15 +86,12 @@ private static Object toAnnotationValue(ScanContext ctx, Object scanAnnotationVa
8586
return result;
8687
}
8788

88-
if (scanAnnotationValue instanceof AnnotationEnumValue anEnum) {
89-
return anEnum.getValueName();
90-
} else if (scanAnnotationValue instanceof AnnotationClassRef aClass) {
91-
return TypeName.create(aClass.getName());
92-
} else if (scanAnnotationValue instanceof AnnotationInfo annotation) {
93-
return createAnnotation(ctx, annotation);
94-
}
89+
return switch (scanAnnotationValue) {
90+
case AnnotationEnumValue anEnum -> EnumValue.create(TypeName.create(anEnum.getClassName()), anEnum.getValueName());
91+
case AnnotationClassRef aClass -> TypeName.create(aClass.getName());
92+
case AnnotationInfo annotation -> createAnnotation(ctx, annotation);
93+
default -> scanAnnotationValue;
94+
};
9595

96-
// supported type
97-
return scanAnnotationValue;
9896
}
9997
}

common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -532,6 +532,14 @@ private static String asString(TypeName typeName, String property, Object value)
532532
return str;
533533
}
534534

535+
if (value instanceof TypeName tn) {
536+
return tn.fqName();
537+
}
538+
539+
if (value instanceof EnumValue ev) {
540+
return ev.name();
541+
}
542+
535543
if (value instanceof List<?>) {
536544
throw new IllegalArgumentException(typeName.fqName() + " property " + property
537545
+ " is a list, cannot be converted to String");
@@ -619,22 +627,33 @@ private static Class<?> asClass(TypeName typeName, String property, Object value
619627
if (value instanceof Class<?> theClass) {
620628
return theClass;
621629
}
622-
if (value instanceof String str) {
623-
try {
624-
return Class.forName(str);
625-
} catch (ClassNotFoundException e) {
626-
throw new IllegalArgumentException(typeName.fqName() + " property " + property
627-
+ " of type String and value \"" + str + "\""
628-
+ " cannot be converted to Class");
629-
}
630+
631+
String className;
632+
633+
if (value instanceof TypeName tn) {
634+
className = tn.fqName();
635+
} else if (value instanceof String str) {
636+
className = str;
637+
} else {
638+
639+
throw new IllegalArgumentException(typeName.fqName() + " property " + property
640+
+ " of type " + value.getClass().getName()
641+
+ " cannot be converted to Class");
630642
}
631643

632-
throw new IllegalArgumentException(typeName.fqName() + " property " + property
633-
+ " of type " + value.getClass().getName()
634-
+ " cannot be converted to Class");
644+
try {
645+
return Class.forName(className);
646+
} catch (ClassNotFoundException e) {
647+
throw new IllegalArgumentException(typeName.fqName() + " property " + property
648+
+ " of type String and value \"" + className + "\""
649+
+ " cannot be converted to Class");
650+
}
635651
}
636652

637653
private static TypeName asTypeName(TypeName typeName, String property, Object value) {
654+
if (value instanceof TypeName tn) {
655+
return tn;
656+
}
638657
if (value instanceof Class<?> theClass) {
639658
return TypeName.create(theClass);
640659
}
@@ -664,6 +683,15 @@ private static <T extends Enum<T>> T asEnum(TypeName typeName, String property,
664683
if (value instanceof String str) {
665684
return Enum.valueOf(type, str);
666685
}
686+
if (value instanceof EnumValue enumValue) {
687+
if (enumValue.type().equals(TypeName.create(type))) {
688+
return Enum.valueOf(type, enumValue.name());
689+
}
690+
691+
throw new IllegalStateException("Property " + property + " is of enum type for enum "
692+
+ enumValue.type().fqName() + ", yet you requested "
693+
+ type.getName());
694+
}
667695

668696
throw new IllegalArgumentException(typeName.fqName() + " property " + property
669697
+ " of type " + value.getClass().getName()
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2024 Oracle and/or its affiliates.
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+
* http://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+
17+
package io.helidon.common.types;
18+
19+
/**
20+
* When creating an {@link io.helidon.common.types.Annotation}, we may need to create an enum value
21+
* without access to the enumeration.
22+
* <p>
23+
* In such a case, you can use this type when calling {@link io.helidon.common.types.Annotation.Builder#putValue(String, Object)}
24+
*/
25+
public interface EnumValue {
26+
/**
27+
* Create a new enum value, when the enum is not available on classpath.
28+
*
29+
* @param enumType type of the enumeration
30+
* @param enumName value of the enumeration
31+
* @return enum value
32+
*/
33+
static EnumValue create(TypeName enumType, String enumName) {
34+
return new EnumValue() {
35+
@Override
36+
public TypeName type() {
37+
return enumType;
38+
}
39+
40+
@Override
41+
public String name() {
42+
return enumName;
43+
}
44+
};
45+
}
46+
47+
/**
48+
* Type of the enumeration.
49+
*
50+
* @return type name of the enumeration
51+
*/
52+
TypeName type();
53+
54+
/**
55+
* The enum value.
56+
*
57+
* @return enum value
58+
*/
59+
String name();
60+
}

0 commit comments

Comments
 (0)