Skip to content

Commit

Permalink
Handling enum values in a consistent way in code generation. (#9167)
Browse files Browse the repository at this point in the history
Handling type values in a consistent way in code generation.
  • Loading branch information
tomas-langer authored Aug 20, 2024
1 parent d8fc80c commit 6665d7c
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -664,6 +664,15 @@ private static <T extends Enum<T>> 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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object, Object> {
private final Elements elements;
private boolean mapVoidToNull;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Objects;
import java.util.Set;

import io.helidon.common.types.EnumValue;
import io.helidon.common.types.TypeName;

/**
Expand All @@ -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);
}

/**
Expand All @@ -48,6 +51,26 @@ void writeComponent(ModelWriter writer, Set<String> 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()) {
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -664,6 +683,15 @@ private static <T extends Enum<T>> 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()
Expand Down
60 changes: 60 additions & 0 deletions common/types/src/main/java/io/helidon/common/types/EnumValue.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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();
}

0 comments on commit 6665d7c

Please sign in to comment.