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

com.workday.postman.codegen.ParceledElementExtractor Maven / Gradle / Ivy

/*
 * Copyright 2015 Workday, Inc.
 *
 * This software is available under the MIT license.
 * Please see the LICENSE.txt file in this project.
 */

package com.workday.postman.codegen;

import com.workday.meta.CodeAnalysisUtils;
import com.workday.meta.MetaTypes;
import com.workday.postman.annotations.NotParceled;
import com.workday.postman.annotations.Parceled;
import com.workday.postman.annotations.PostCreateChild;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;

/**
 * A helper class that runs validation on classes or fields annotated with {@literal@}{@link
 * Parceled} and extracts all valid fields eligible to be included in the Parcel.
 *
 * @author nathan.taylor
 * @since 2013-10-8
 */
class ParceledElementExtractor {

    private final ProcessingEnvironment processingEnv;
    private final Messager messager;
    private final MetaTypes metatypes;

    public ParceledElementExtractor(ProcessingEnvironment processingEnv) {

        this.processingEnv = processingEnv;
        metatypes = new MetaTypes(processingEnv);
        messager = processingEnv.getMessager();
    }

    /**
     * Finds and returns all valid methods annotated with {@literal@}{@link PostCreateChild}.
     *
     * @param classToParcel The element representing the class that will be parceled.
     */
    public Collection extractPostCreateChildMethods(TypeElement classToParcel) {
        Collection allMethods = ElementFilter.methodsIn(
                processingEnv.getElementUtils().getAllMembers(classToParcel));

        Collection postCreateChildMethods = new ArrayList<>();
        for (ExecutableElement method : allMethods) {
            PostCreateChild annotation = method.getAnnotation(PostCreateChild.class);
            if (annotation != null && assertValidPostCreateMethod(method)) {
                postCreateChildMethods.add(method);
            }
        }

        return postCreateChildMethods;
    }

    /**
     * Returns {@code true} if the given method is valid for the {@link PostCreateChild} annotation,
     * and {@code false}. If the method is not valid, this method will generate all the relevant
     * compilation errors.
     */
    private boolean assertValidPostCreateMethod(ExecutableElement method) {
        boolean isValid = true;
        if (CodeAnalysisUtils.isPrivate(method)) {
            isValid = false;
            String message = String.format("Method annotated with @%s cannot be private.",
                                           PostCreateChild.class.getSimpleName());
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, method);
        }

        if (CodeAnalysisUtils.isStatic(method)) {
            isValid = false;
            String message = String.format("Method annotated with @%s cannot be static.",
                                           PostCreateChild.class.getSimpleName());
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, method);
        }

        if (method.getParameters().size() != 1
                || !metatypes.isSameType(method.getParameters().get(0).asType(), Object.class)) {
            isValid = false;
            String message = String.format(Locale.US,
                                           "Method annotated with @%s must take a single argument"
                                                   + " of type Object.",
                                           PostCreateChild.class.getSimpleName());
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, method);
        }

        return isValid;
    }

    /**
     * Extracts all fields that should be included in the parcel. If {@code classToParcel} is
     * annotated with {@literal@}{@link Parceled}, then all fields except those that are final,
     * private, or annotated with {@literal@}{@link NotParceled} will be included. Otherwise, all
     * fields annotated with {@literal@}Parceled are included.
     *
     * @param classToParcel The element representing the class that will be parceled.
     *
     * @return A collection of all fields that should be included in the parcel.
     */
    public Collection extractParceledFields(TypeElement classToParcel) {
        Collection allFields = ElementFilter.fieldsIn(
                processingEnv.getElementUtils().getAllMembers(classToParcel));

        if (hasParceledAnnotation(classToParcel)) {
            return extractValidFields(allFields);
        } else {
            return extractAnnotatedFields(allFields);
        }
    }

    private Collection extractValidFields(Collection allFields) {
        Collection validFields = new ArrayList(allFields.size());
        for (VariableElement field : allFields) {
            boolean shouldAdd = true;

            if (CodeAnalysisUtils.isConstant(field) || hasNotParceledAnnotation(field)) {
                shouldAdd = false;
            } else {
                if (CodeAnalysisUtils.isPrivate(field)) {
                    String message = String.format("Parceler will ignore private field %s.",
                                                   field.getSimpleName());
                    messager.printMessage(Diagnostic.Kind.WARNING, message, field);
                    shouldAdd = false;
                }

                if (CodeAnalysisUtils.isFinal(field)) {
                    String message = String.format("Parceler will ignore final field %s.",
                                                   field.getSimpleName());
                    messager.printMessage(Diagnostic.Kind.WARNING, message, field);
                    shouldAdd = false;
                }

                if (hasParceledAnnotation(field)) {
                    String message = String.format(
                            "@%s annotations are ignored when the enclosing class is annotated "
                                    + "with @%s.",
                            Parceled.class.getSimpleName(), Parceled.class.getSimpleName());
                    messager.printMessage(Diagnostic.Kind.WARNING, message, field);
                }
            }

            if (shouldAdd) {
                validFields.add(field);
            }
        }
        return validFields;
    }

    private Collection extractAnnotatedFields(Collection
                                                                       allFields) {
        Collection validAnnotatedFields =
                new ArrayList(allFields.size());

        for (VariableElement field : allFields) {
            boolean shouldAdd = hasParceledAnnotation(field);

            if (hasNotParceledAnnotation(field)) {
                String message = String.format(
                        "@%s annotations are ignored when the enclosing class is not annotated "
                                + "with @%s",
                        NotParceled.class.getSimpleName(),
                        Parceled.class.getSimpleName());
                messager.printMessage(Diagnostic.Kind.WARNING, message, field);
            }

            if (hasParceledAnnotation(field) && CodeAnalysisUtils.isPrivate(field)) {
                String message = "Cannot access private fields when parceling.";
                messager.printMessage(Diagnostic.Kind.ERROR, message, field);
                shouldAdd = false;
            }

            if (hasParceledAnnotation(field) && CodeAnalysisUtils.isFinal(field)) {
                String message = "Cannot access final fields when parceling.";
                messager.printMessage(Diagnostic.Kind.ERROR, message, field);
                shouldAdd = false;
            }

            if (shouldAdd) {
                validAnnotatedFields.add(field);
            }
        }
        return validAnnotatedFields;
    }

    private boolean hasNotParceledAnnotation(Element element) {
        return element.getAnnotation(NotParceled.class) != null;
    }

    private boolean hasParceledAnnotation(Element element) {
        return element.getAnnotation(Parceled.class) != null;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy