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

com.gabrielittner.auto.value.util.AutoValueUtil Maven / Gradle / Ivy

There is a newer version: 0.4.0
Show newest version
package com.gabrielittner.auto.value.util;

import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.auto.value.extension.AutoValueExtension.Context;
import com.google.common.collect.Lists;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.tools.Diagnostic;

import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.tools.Diagnostic.Kind.ERROR;

/**
 * Convenience and boilerplate methods for implementations of {@link AutoValueExtension}.
 */
public final class AutoValueUtil {

    /**
     * Returns the {@link ClassName} of the class annotated with {@link AutoValue}.
     */
    public static ClassName getAutoValueClassClassName(Context context) {
        return ClassName.get(context.autoValueClass());
    }

    private static String getFinalClassSimpleName(Context context) {
        TypeElement autoValueClass = context.autoValueClass();
        String name = autoValueClass.getSimpleName().toString();

        Element enclosingElement = autoValueClass.getEnclosingElement();
        while (enclosingElement instanceof TypeElement) {
            name = enclosingElement.getSimpleName().toString() + "_" + name;
            enclosingElement = enclosingElement.getEnclosingElement();
        }

        return "AutoValue_" + name;
    }

    /**
     * Returns the {@link ClassName} of the last class in the chain of generated implementations,
     * which is final. This can be used for creating new instances.
     */
    public static ClassName getFinalClassClassName(Context context) {
        return ClassName.get(context.packageName(), getFinalClassSimpleName(context));
    }

    /**
     * Creates a new {@link TypeSpec.Builder} for the class that is generated by the extension.
     * It will add a final or abstract modifier, the superclass and a constructor that calls super.
     * The returned TypeSpec will also include all TypeVariables if the AutoValue class is generic.
     */
    public static TypeSpec.Builder newTypeSpecBuilder(Context context, String className,
            String classToExtend, boolean isFinal) {
        TypeVariableName[] typeVariables = getTypeVariables(context.autoValueClass());
        return TypeSpec.classBuilder(className)
                .addModifiers(isFinal ? FINAL : ABSTRACT)
                .addTypeVariables(Arrays.asList(typeVariables))
                .superclass(getSuperClass(context.packageName(), classToExtend, typeVariables))
                .addMethod(newConstructor(context.properties()));
    }

    private static TypeVariableName[] getTypeVariables(TypeElement autoValueClass) {
        List parameters = autoValueClass.getTypeParameters();
        TypeVariableName[] typeVariables = new TypeVariableName[parameters.size()];
        for (int i = 0, length = typeVariables.length; i < length; i++) {
            typeVariables[i] = TypeVariableName.get(parameters.get(i));
        }
        return typeVariables;
    }

    private static TypeName getSuperClass(String packageName, String classToExtend,
            TypeName[] typeVariables) {
        ClassName superClassWithoutParameters = ClassName.get(packageName, classToExtend);
        if (typeVariables.length > 0) {
            return ParameterizedTypeName.get(superClassWithoutParameters, typeVariables);
        } else {
            return superClassWithoutParameters;
        }
    }

    private static MethodSpec newConstructor(Map properties) {
        List params = Lists.newArrayList();
        for (Map.Entry entry : properties.entrySet()) {
            TypeName typeName = TypeName.get(entry.getValue().getReturnType());
            params.add(ParameterSpec.builder(typeName, entry.getKey()).build());
        }

        CodeBlock code = newConstructorCall(CodeBlock.of("super"), properties.keySet().toArray());

        return MethodSpec.constructorBuilder()
                .addParameters(params)
                .addCode(code)
                .build();
    }

    /**
     * Creates a {@link CodeBlock} that calls the constructor of the final generated class
     * {@link AutoValueUtil#getFinalClassClassName(Context)}. The given {@code properties} array
     * should either contain plain {@code String}'s or
     * JavaPoet Names.
     */
    public static CodeBlock newFinalClassConstructorCall(Context context, Object[] properties) {
        CodeBlock constructorName = CodeBlock.of("new $T", getFinalClassClassName(context));
        return newConstructorCall(constructorName, properties);
    }

    private static CodeBlock newConstructorCall(CodeBlock constructorName, Object[] properties) {
        StringBuilder params = new StringBuilder("(");
        for (int i = properties.length; i > 0; i--) {
            params.append("$N");
            if (i > 1) params.append(", ");
        }
        params.append(")");
        return CodeBlock.builder()
                .add(constructorName)
                .addStatement(params.toString(), properties)
                .build();
    }

    /**
     * Will call {@link Messager#printMessage(Diagnostic.Kind, CharSequence, Element)} with
     * {@link Diagnostic.Kind#ERROR} and the given {@code message} for {@code property}.
     * This will ultimately fail the build, but will not abort it right now.
     */
    public static void error(Context context, Property property, String message) {
        context.processingEnvironment().getMessager()
                .printMessage(ERROR, message, property.element());
    }

    /**
     * Will call {@link Messager#printMessage(Diagnostic.Kind, CharSequence, Element)} with
     * {@link Diagnostic.Kind#ERROR} and the given {@code message} formatted using {@code args}
     * for {@code property}.
     * This will ultimately fail the build, but will not abort it right now.
     */
    public static void error(Context context, Property property, String message, Object... args) {
        error(context, property, String.format(message, args));
    }

    private AutoValueUtil() {
        throw new AssertionError("No instances.");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy