All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.sourcegen.bytecode.EnumGenUtils Maven / Gradle / Ivy

There is a newer version: 1.5.3
Show newest version
/*
 * Copyright 2017-2024 original authors
 *
 * 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
 *
 * https://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.micronaut.sourcegen.bytecode;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.sourcegen.model.ClassDef;
import io.micronaut.sourcegen.model.ClassTypeDef;
import io.micronaut.sourcegen.model.EnumDef;
import io.micronaut.sourcegen.model.ExpressionDef;
import io.micronaut.sourcegen.model.FieldDef;
import io.micronaut.sourcegen.model.MethodDef;
import io.micronaut.sourcegen.model.ObjectDef;
import io.micronaut.sourcegen.model.ParameterDef;
import io.micronaut.sourcegen.model.StatementDef;
import io.micronaut.sourcegen.model.TypeDef;

import javax.lang.model.element.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * The enum generator utils.
 *
 * @author Denis Stepanov
 * @since 1.5
 */
@Internal
public class EnumGenUtils {

    private static final java.lang.reflect.Method CLONE_METHOD = ReflectionUtils.getRequiredMethod(Object.class, "clone");
    private static final java.lang.reflect.Method ENUM_VALUE_OF_METHOD = ReflectionUtils.getRequiredMethod(Enum.class, "valueOf", Class.class, String.class);
    private static final java.lang.reflect.Constructor ENUM_CONSTRUCTOR = ReflectionUtils.getRequiredInternalConstructor(Enum.class, String.class, int.class);

    /**
     * Generate the {@link ClassDef} from {@link EnumDef}.
     *
     * @param enumDef The enum def
     * @return The class definition
     */
    public static ClassDef toClassDef(EnumDef enumDef) {
        ClassTypeDef enumTypeDef = ClassTypeDef.of(enumDef.getName());

        ClassTypeDef baseEnumTypeDef = ClassTypeDef.of(Enum.class);

        ClassDef.ClassDefBuilder classDefBuilder = ClassDef.builder(enumDef.getName())
            .addFields(enumDef.getFields())
            .addModifiers(enumDef.getModifiers())
            .addModifiers(Modifier.FINAL)
            .superclass(TypeDef.parameterized(baseEnumTypeDef, enumTypeDef))
            .addSuperinterfaces(enumDef.getSuperinterfaces())
            .addInnerType(enumDef.getInnerTypes());

        int i = 0;
        for (Map.Entry> e : enumDef.getEnumConstants().entrySet()) {

            List values = new ArrayList<>();
            values.add(ExpressionDef.constant(e.getKey()));
            values.add(TypeDef.Primitive.INT.constant(i++));
            values.addAll(e.getValue());

            FieldDef enumField = FieldDef.builder(e.getKey(), enumTypeDef)
                .addModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PUBLIC)
                .initializer(enumTypeDef.instantiate(values))
                .build();

            classDefBuilder.addField(enumField);
        }

        int constructorIndex = 0;
        boolean constructorAdded = false;
        for (MethodDef method : enumDef.getMethods()) {
            if (!method.isConstructor()) {
                continue;
            }
            addEnumConstructor(classDefBuilder, method, constructorIndex);
            constructorAdded = true;
        }
        if (!constructorAdded) {
            classDefBuilder.addMethod(MethodDef.override(ENUM_CONSTRUCTOR)
                .overrideModifiers(Modifier.PRIVATE)
                .build((aThis, methodParameters) ->
                    aThis.superRef().invokeConstructor(ENUM_CONSTRUCTOR, methodParameters.get(0), methodParameters.get(1))));
        }

        MethodDef internalValuesMethod = MethodDef.builder("$values")
            .addModifiers(Modifier.STATIC, Modifier.PRIVATE)
            .build((aThis, methodParameters) ->
                enumTypeDef.array()
                    .instantiate(
                        enumDef.getEnumConstants()
                            .keySet()
                            .stream()
                            .map(name -> enumTypeDef.getStaticField(name, enumTypeDef))
                            .toList()
                    )
                    .returning());

        classDefBuilder.addMethod(internalValuesMethod);

        FieldDef valuesField = FieldDef.builder("$VALUES").ofType(enumTypeDef.array())
            .addModifiers(Modifier.STATIC, Modifier.PRIVATE)
            .initializer(enumTypeDef.invokeStatic(internalValuesMethod))
            .build();

        classDefBuilder.addField(valuesField);

        classDefBuilder.addMethod(MethodDef.builder("values")
            .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
            .returns(enumTypeDef.array())
            .build((aThis2, methodParameters2) ->
                enumTypeDef.getStaticField(valuesField)
                    .invoke(CLONE_METHOD)
                    .cast(enumTypeDef.array())
                    .returning()));

        classDefBuilder.addMethod(MethodDef.builder("valueOf")
            .addParameter(ParameterDef.of("value", TypeDef.STRING))
            .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
            .build((aThis, methodParameters) ->
                baseEnumTypeDef
                    .invokeStatic(ENUM_VALUE_OF_METHOD, ExpressionDef.constant(enumTypeDef), methodParameters.get(0))
                    .cast(enumTypeDef)
                    .returning()));

        enumDef.getMethods().stream().filter(m -> !m.isConstructor()).forEach(classDefBuilder::addMethod);

        return classDefBuilder.build();
    }

    private static void addEnumConstructor(ClassDef.ClassDefBuilder classDefBuilder, MethodDef method, int constructorIndex) {
        // To avoid modifying the created constructor, we will create a method and invoke it from the modified enum constructor
        MethodDef constructorMethod = MethodDef.builder("$constructor" + constructorIndex)
            .addModifiers(Modifier.PRIVATE)
            .addParameters(method.getParameters())
            .returns(TypeDef.VOID)
            .addStatements(method.getStatements())
            .build();
        classDefBuilder.addMethod(constructorMethod);

        classDefBuilder.addMethod(MethodDef.constructor()
            .addModifiers(Modifier.PRIVATE)
            .addParameters(ENUM_CONSTRUCTOR.getParameterTypes())
            .addParameters(method.getParameters())
            .build((aThis, methodParameters) -> StatementDef.multi(
                aThis.superRef().invokeConstructor(ENUM_CONSTRUCTOR, methodParameters.get(0), methodParameters.get(1)),
                aThis.invoke(constructorMethod, methodParameters.subList(2, methodParameters.size()))
            )));
    }

    /**
     * Is enum field.
     *
     * @param objectDef The object def
     * @param fieldDef  The field
     * @return true if is an enum field
     */
    public static boolean isEnumField(ObjectDef objectDef, FieldDef fieldDef) {
        Optional initializer = fieldDef.getInitializer();
        return objectDef instanceof ClassDef classDef && isEnum(classDef)
            && initializer.isPresent()
            && initializer.get() instanceof ExpressionDef.NewInstance ni
            && ni.type().getName().equals(classDef.getName());
    }

    /**
     * Is enum class.
     *
     * @param classDef The class def
     * @return true if the enum class
     */
    public static boolean isEnum(ClassDef classDef) {
        return classDef.getSuperclass() != null && classDef.getSuperclass().getName().equals(Enum.class.getName());
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy