diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java index 0101b393de7..52501d9d95e 100644 --- a/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/AnnotationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -664,6 +664,15 @@ private static > T asEnum(TypeName typeName, String property, if (value instanceof String str) { return Enum.valueOf(type, str); } + if (value instanceof EnumValue enumValue) { + if (enumValue.type().equals(TypeName.create(type))) { + return Enum.valueOf(type, enumValue.name()); + } + + throw new IllegalStateException("Property " + property + " is of enum type for enum " + + enumValue.type().fqName() + ", yet you requested " + + type.getName()); + } throw new IllegalArgumentException(typeName.fqName() + " property " + property + " of type " + value.getClass().getName() diff --git a/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java b/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java new file mode 100644 index 00000000000..34fa57b6990 --- /dev/null +++ b/builder/tests/common-types/src/main/java/io/helidon/common/types/EnumValue.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.types; + +/** + * When creating an {@link io.helidon.common.types.Annotation}, we may need to create an enum value + * without access to the enumeration. + *

+ * In such a case, you can use this type when calling {@link io.helidon.common.types.Annotation.Builder#putValue(String, Object)} + */ +public interface EnumValue { + /** + * Create a new enum value, when the enum is not available on classpath. + * + * @param enumType type of the enumeration + * @param enumName value of the enumeration + * @return enum value + */ + static EnumValue create(TypeName enumType, String enumName) { + return new EnumValue() { + @Override + public TypeName type() { + return enumType; + } + + @Override + public String name() { + return enumName; + } + }; + } + + /** + * Type of the enumeration. + * + * @return type name of the enumeration + */ + TypeName type(); + + /** + * The enum value. + * + * @return enum value + */ + String name(); +} diff --git a/codegen/apt/src/main/java/io/helidon/codegen/apt/ToAnnotationValueVisitor.java b/codegen/apt/src/main/java/io/helidon/codegen/apt/ToAnnotationValueVisitor.java index 58cb031ceec..15f8e5f0bc6 100644 --- a/codegen/apt/src/main/java/io/helidon/codegen/apt/ToAnnotationValueVisitor.java +++ b/codegen/apt/src/main/java/io/helidon/codegen/apt/ToAnnotationValueVisitor.java @@ -28,6 +28,9 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; +import io.helidon.common.types.EnumValue; +import io.helidon.common.types.TypeNames; + class ToAnnotationValueVisitor implements AnnotationValueVisitor { private final Elements elements; private boolean mapVoidToNull; @@ -126,18 +129,30 @@ public Object visitString(String s, Object o) { return s; } + @SuppressWarnings("removal") @Override public Object visitType(TypeMirror t, Object o) { - String val = t.toString(); - if (mapVoidToNull && ("void".equals(val) || Void.class.getName().equals(val))) { - val = null; + var maybeType = AptTypeFactory.createTypeName(t); + if (maybeType.isEmpty()) { + return null; + } + var type = maybeType.get(); + if (mapVoidToNull && (type.equals(TypeNames.BOXED_VOID) || type.equals(TypeNames.PRIMITIVE_VOID))) { + return null; } - return val; + return type; } + @SuppressWarnings("removal") @Override public Object visitEnumConstant(VariableElement c, Object o) { - return String.valueOf(c.getSimpleName()); + var maybeType = AptTypeFactory.createTypeName(c.getEnclosingElement()); + + if (maybeType.isEmpty()) { + // this will be one-way only + return String.valueOf(c.getSimpleName()); + } + return EnumValue.create(maybeType.get(), String.valueOf(c.getSimpleName())); } @Override diff --git a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java index b4a28580262..23299d70815 100644 --- a/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java +++ b/codegen/class-model/src/main/java/io/helidon/codegen/classmodel/AnnotationParameter.java @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Set; +import io.helidon.common.types.EnumValue; import io.helidon.common.types.TypeName; /** @@ -27,10 +28,12 @@ public final class AnnotationParameter extends CommonComponent { private final String value; + private final TypeName importedType; private AnnotationParameter(Builder builder) { super(builder); this.value = resolveValueToString(builder.type(), builder.value); + this.importedType = resolveImport(builder.value); } /** @@ -48,6 +51,26 @@ void writeComponent(ModelWriter writer, Set declaredTokens, ImportOrgani writer.write(name() + " = " + value); } + @Override + void addImports(ImportOrganizer.Builder imports) { + if (importedType != null) { + imports.addImport(importedType); + } + } + + private static TypeName resolveImport(Object value) { + if (value.getClass().isEnum()) { + return TypeName.create(value.getClass()); + } + if (value instanceof TypeName tn) { + return tn; + } + if (value instanceof EnumValue ev) { + return ev.type(); + } + return null; + } + private static String resolveValueToString(Type type, Object value) { Class valueClass = value.getClass(); if (valueClass.isEnum()) { @@ -58,7 +81,9 @@ private static String resolveValueToString(Type type, Object value) { return "\"" + stringValue + "\""; } } else if (value instanceof TypeName typeName) { - return typeName.fqName() + ".class"; + return typeName.classNameWithEnclosingNames() + ".class"; + } else if (value instanceof EnumValue enumValue) { + return enumValue.type().classNameWithEnclosingNames() + "." + enumValue.name(); } return value.toString(); } diff --git a/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java b/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java index f7fd864644c..6861e4d6b2d 100644 --- a/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java +++ b/codegen/scan/src/main/java/io/helidon/codegen/scan/ScanAnnotationFactory.java @@ -23,6 +23,7 @@ import java.util.Map; import io.helidon.common.types.Annotation; +import io.helidon.common.types.EnumValue; import io.helidon.common.types.TypeName; import io.github.classgraph.AnnotationClassRef; @@ -85,15 +86,12 @@ private static Object toAnnotationValue(ScanContext ctx, Object scanAnnotationVa return result; } - if (scanAnnotationValue instanceof AnnotationEnumValue anEnum) { - return anEnum.getValueName(); - } else if (scanAnnotationValue instanceof AnnotationClassRef aClass) { - return TypeName.create(aClass.getName()); - } else if (scanAnnotationValue instanceof AnnotationInfo annotation) { - return createAnnotation(ctx, annotation); - } + return switch (scanAnnotationValue) { + case AnnotationEnumValue anEnum -> EnumValue.create(TypeName.create(anEnum.getClassName()), anEnum.getValueName()); + case AnnotationClassRef aClass -> TypeName.create(aClass.getName()); + case AnnotationInfo annotation -> createAnnotation(ctx, annotation); + default -> scanAnnotationValue; + }; - // supported type - return scanAnnotationValue; } } diff --git a/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java b/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java index 0101b393de7..242940fb555 100644 --- a/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java +++ b/common/types/src/main/java/io/helidon/common/types/AnnotationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. + * Copyright (c) 2023, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * 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) return str; } + if (value instanceof TypeName tn) { + return tn.fqName(); + } + + if (value instanceof EnumValue ev) { + return ev.name(); + } + if (value instanceof List) { throw new IllegalArgumentException(typeName.fqName() + " property " + property + " is a list, cannot be converted to String"); @@ -619,22 +627,33 @@ private static Class asClass(TypeName typeName, String property, Object value if (value instanceof Class theClass) { return theClass; } - if (value instanceof String str) { - try { - return Class.forName(str); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException(typeName.fqName() + " property " + property - + " of type String and value \"" + str + "\"" - + " cannot be converted to Class"); - } + + String className; + + if (value instanceof TypeName tn) { + className = tn.fqName(); + } else if (value instanceof String str) { + className = str; + } else { + + throw new IllegalArgumentException(typeName.fqName() + " property " + property + + " of type " + value.getClass().getName() + + " cannot be converted to Class"); } - throw new IllegalArgumentException(typeName.fqName() + " property " + property - + " of type " + value.getClass().getName() - + " cannot be converted to Class"); + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(typeName.fqName() + " property " + property + + " of type String and value \"" + className + "\"" + + " cannot be converted to Class"); + } } private static TypeName asTypeName(TypeName typeName, String property, Object value) { + if (value instanceof TypeName tn) { + return tn; + } if (value instanceof Class theClass) { return TypeName.create(theClass); } @@ -664,6 +683,15 @@ private static > T asEnum(TypeName typeName, String property, if (value instanceof String str) { return Enum.valueOf(type, str); } + if (value instanceof EnumValue enumValue) { + if (enumValue.type().equals(TypeName.create(type))) { + return Enum.valueOf(type, enumValue.name()); + } + + throw new IllegalStateException("Property " + property + " is of enum type for enum " + + enumValue.type().fqName() + ", yet you requested " + + type.getName()); + } throw new IllegalArgumentException(typeName.fqName() + " property " + property + " of type " + value.getClass().getName() diff --git a/common/types/src/main/java/io/helidon/common/types/EnumValue.java b/common/types/src/main/java/io/helidon/common/types/EnumValue.java new file mode 100644 index 00000000000..34fa57b6990 --- /dev/null +++ b/common/types/src/main/java/io/helidon/common/types/EnumValue.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.types; + +/** + * When creating an {@link io.helidon.common.types.Annotation}, we may need to create an enum value + * without access to the enumeration. + *

+ * In such a case, you can use this type when calling {@link io.helidon.common.types.Annotation.Builder#putValue(String, Object)} + */ +public interface EnumValue { + /** + * Create a new enum value, when the enum is not available on classpath. + * + * @param enumType type of the enumeration + * @param enumName value of the enumeration + * @return enum value + */ + static EnumValue create(TypeName enumType, String enumName) { + return new EnumValue() { + @Override + public TypeName type() { + return enumType; + } + + @Override + public String name() { + return enumName; + } + }; + } + + /** + * Type of the enumeration. + * + * @return type name of the enumeration + */ + TypeName type(); + + /** + * The enum value. + * + * @return enum value + */ + String name(); +}