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

com.atlassian.bamboo.specs.codegen.emitters.fragment.FieldSetterEmitterFactory Maven / Gradle / Ivy

There is a newer version: 10.1.0
Show newest version
package com.atlassian.bamboo.specs.codegen.emitters.fragment;


import com.atlassian.bamboo.specs.api.builders.EntityPropertiesBuilder;
import com.atlassian.bamboo.specs.api.codegen.CodeEmitter;
import com.atlassian.bamboo.specs.api.codegen.CodeGenerationContext;
import com.atlassian.bamboo.specs.api.codegen.CodeGenerationException;
import com.atlassian.bamboo.specs.api.codegen.annotations.NoArgCallForBooleanValue;
import com.atlassian.bamboo.specs.api.codegen.annotations.Secret;
import com.atlassian.bamboo.specs.api.codegen.annotations.Setter;
import com.atlassian.bamboo.specs.api.model.EntityProperties;
import com.atlassian.bamboo.specs.codegen.BuilderClassProvider;
import com.atlassian.bamboo.specs.codegen.emitters.CodeGenerationUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * Utility to resolve the best code generator for a field of {@link EntityProperties} instance.
 */
public final class FieldSetterEmitterFactory {
    private FieldSetterEmitterFactory() {
    }

    private static Object getFirstElement(final Iterable value) {
        return value.iterator().next(); //!called in context when we know element exists;
    }

    private static CodeEmitter findSetterForField(final Class builderClass,
                                                  final Field field,
                                                  final String name,
                                                  final Object value,
                                                  boolean strictNameMatch) throws CodeGenerationException {
        Class type = field.getType().isPrimitive() ? field.getType() : value.getClass();
        String expectedMethodName = name;
        Class argumentType = type;
        Class arrayElementClass = null;

        if (EntityProperties.class.isAssignableFrom(type)) {
            argumentType = BuilderClassProvider.findBuilderClass(type);
            if (argumentType == null) {
                throw new CodeGenerationException("Could not find builder for field " + field.getName() + " of class " + type.getCanonicalName());
            }
            if (!strictNameMatch) {
                expectedMethodName = expectedMethodName.replace("Properties", "");
            }
        } else if (Iterable.class.isAssignableFrom(argumentType)) {
            arrayElementClass = getFirstElement((Iterable) value).getClass();
            if (EntityProperties.class.isAssignableFrom(arrayElementClass)) {
                arrayElementClass = BuilderClassProvider.findBuilderClass(arrayElementClass);
            }
        }

        int matchingMethodCount = 0;
        Method candidateMethod = null;
        for (Method method : builderClass.getMethods()) {
            if (method.getDeclaringClass() == Object.class) {
                continue; //we don't want any equals an the likes
            }
            if (method.getParameters().length == 1) {
                final Parameter parameter = method.getParameters()[0];
                if (expectedMethodName.equals(method.getName()) && isAssignableToParameterType(argumentType, parameter)) {
                    return createFieldSetterEmitter(field, method);
                }

                if (expectedMethodName.equals(method.getName()) && Iterable.class.isAssignableFrom(argumentType)) {
                    if (parameter.getType().isArray()) {
                        if (parameter.getType().getComponentType().isAssignableFrom(arrayElementClass)) {
                            return (CodeEmitter) new VarargArgumentSetterEmitter(method.getName());
                        } else {
                            continue;
                        }
                    }
                } else if (!strictNameMatch && EntityPropertiesBuilder.class.isAssignableFrom(argumentType) && isAssignableToParameterType(argumentType, parameter)) {
                    //for bamboo entity classes only: if we find exact argument match and there's only one such method, this is it
                    matchingMethodCount++;
                    candidateMethod = method;
                } else if (!strictNameMatch && StringUtils.containsIgnoreCase(method.getName(), name) && isAssignableToParameterType(argumentType, parameter)) {
                    //name looks sort of correct...
                    matchingMethodCount++;
                    candidateMethod = method;
                }
            }
        }
        if (matchingMethodCount == 1) {
            return createFieldSetterEmitter(field, candidateMethod);
        }
        throw new CodeGenerationException("Could not find suitable setter for field " + field.getName() + " expected method called " + expectedMethodName);
    }

    private static CodeEmitter createFieldSetterEmitter(Field field, Method method) {
        return field.isAnnotationPresent(Secret.class) ?
                new SecretSetterEmitter(method.getName())
                :
                new FieldSetterEmitter(method.getName());
    }

    private static boolean isAssignableToParameterType(Class valueType, Parameter parameter) {
        Class parameterType = parameter.getType();
        if (parameterType.isAssignableFrom(valueType)) {
            return true;
        }
        if (parameterType == Boolean.class && valueType == Boolean.TYPE) {
            return true;
        }
        if (parameterType == Long.class && valueType == Long.TYPE) {
            return true;
        }
        if (parameterType == Integer.class && valueType == Integer.TYPE) {
            return true;
        }
        return false;
    }

    private static boolean isBoolean(Field field) {
        return field.getType() == Boolean.TYPE || field.getType() == Boolean.class;
    }

    private static String findNoArgMethodName(Class builderClass, Field field, String nameFromAnnotation) throws CodeGenerationException {
        final String expectedMethodName = StringUtils.isNotBlank(nameFromAnnotation) ? nameFromAnnotation : field.getName();
        try {
            Method method = builderClass.getMethod(field.getName());
        } catch (NoSuchMethodException e) {
            throw new CodeGenerationException("Could not find suitable setter for field " + field.getName() + " expected no arguments method called " + expectedMethodName);
        }
        return expectedMethodName;
    }

    public static CodeEmitter fieldSetterEmitterFor(final CodeGenerationContext context,
                                                    final Class builderClass,
                                                    final Field field,
                                                    final Object fieldValue) throws CodeGenerationException {
        final CodeEmitter codeEmitter = CodeGenerationUtils.findEmitterByAnnotation(field);
        if (codeEmitter != null) {
            return codeEmitter;
        }

        if (isBoolean(field) && field.isAnnotationPresent(NoArgCallForBooleanValue.class)) {
            NoArgCallForBooleanValue annotation = field.getAnnotation(NoArgCallForBooleanValue.class);
            boolean annotationValue = annotation.value();
            if (fieldValue.equals(annotationValue)) {
                return new NoArgCallEmitter<>(findNoArgMethodName(builderClass, field, annotation.name()));
            }
        }

        if (field.isAnnotationPresent(Setter.class)) {
            final String setterName = field.getAnnotation(Setter.class).value();
            return findSetterForField(builderClass, field, setterName, fieldValue, true);
        }
        return findSetterForField(builderClass, field, field.getName(), fieldValue, false);
    }


}