fr.xebia.extras.selma.codegen.FactoryWrapper.java Source code

Java tutorial

Introduction

Here is the source code for fr.xebia.extras.selma.codegen.FactoryWrapper.java

Source

/*
 * Copyright 2013  Sven Le Mesle
 *
 * 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 fr.xebia.extras.selma.codegen;

import com.squareup.javawriter.JavaWriter;
import fr.xebia.extras.selma.IoC;

import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;

import static fr.xebia.extras.selma.codegen.MappingSourceNode.callGenericFactoryOut;
import static fr.xebia.extras.selma.codegen.MappingSourceNode.callStaticFactoryOut;
import static javax.lang.model.element.Modifier.*;

/**
 *
 */
public class FactoryWrapper {
    public static final String FACTORY_FIELD_TPL = "factory%s";
    public static final String WITH_FACTORIES = "withFactories";

    private final Element annotatedElement;
    private final AnnotationWrapper annotationWrapper;
    private final MapperGeneratorContext context;
    private final HashMap<Factory, String> unusedFactories;
    private final IoC ioC;
    private final List<TypeElement> factoryFields;
    private final ArrayList<Factory> staticFactories;
    private final ArrayList<Factory> genericFactories;

    public FactoryWrapper(AnnotationWrapper mapperAnnotation, MapperGeneratorContext context) {
        this.annotatedElement = mapperAnnotation.getAnnotatedElement();
        this.annotationWrapper = mapperAnnotation;
        this.context = context;
        this.unusedFactories = new HashMap();
        factoryFields = new ArrayList<TypeElement>();

        staticFactories = new ArrayList<Factory>();
        genericFactories = new ArrayList<Factory>();
        ioC = IoC.valueOf(annotationWrapper.getAsString(MapperWrapper.WITH_IOC));

        collectFactories();

    }

    private void collectFactories() {
        List<String> factoryClasses = annotationWrapper.getAsStrings(WITH_FACTORIES);
        if (factoryClasses.size() > 0) {
            int factoryMethodCount = 0;

            for (String factoryClass : factoryClasses) {

                final TypeElement element = context.elements
                        .getTypeElement(factoryClass.replaceAll("\\.class$", ""));

                factoryMethodCount += collectFactoryMethods(element, false);

                if (factoryMethodCount == 0) {
                    context.error(element, "No valid factory method found in Factory selma class %s\\n "
                            + "A factory method method is public and returns a type not void, it takes a Class object or "
                            + "no parameter at all.", factoryClass);
                } else {

                    TypeConstructorWrapper constructorWrapper = new TypeConstructorWrapper(context, element);
                    if (!constructorWrapper.hasDefaultConstructor && element.getKind() != ElementKind.INTERFACE) {
                        context.error(element, "No default public constructor found in custom mapping class %s\\n"
                                + " Please add one", factoryClass);
                    }

                    // Here we collect the name of the field to create in the Mapper generated class
                    factoryFields.add(element);
                }

            }
        }

    }

    private int collectFactoryMethods(TypeElement element, boolean ignoreAbstract) {
        int factoryMethodCount = 0;
        final List<ExecutableElement> methods = ElementFilter.methodsIn(element.getEnclosedElements());
        for (ExecutableElement method : methods) {
            MethodWrapper methodWrapper = new MethodWrapper(method, (DeclaredType) element.asType(), context);
            // We should ignore abstract methods if parsing an abstract mapper class
            if (ignoreAbstract && methodWrapper.isAbstract()) {
                continue;
            }
            if (isValidFactoryMethod(methodWrapper)) {
                pushFactoryMethod(element, methodWrapper, ignoreAbstract);
                factoryMethodCount++;
            }
        }

        return factoryMethodCount;
    }

    private void pushFactoryMethod(TypeElement element, MethodWrapper method, boolean ignoreAbstract) {
        MappingBuilder res = null;
        String factoryFieldName = ignoreAbstract ? "this" : buildFactoryFieldName(element);

        TypeMirror returnType = method.returnType();
        String methodCall = String.format("%s.%s", factoryFieldName, method.getSimpleName());
        Factory factory = new Factory(this.context, method, methodCall);
        if (method.hasTypeParameter()) {
            genericFactories.add(factory);
        } else {
            staticFactories.add(factory);
        }

        unusedFactories.put(factory, String.format("%s.%s", element.getQualifiedName(), method.getSimpleName()));
    }

    private boolean isValidFactoryMethod(MethodWrapper methodWrapper) {
        boolean res = true;

        if (MapperProcessor.exclusions.contains(methodWrapper.getSimpleName())) {
            // We skip excluded methods
            return false;
        }

        if (!methodWrapper.element().getModifiers().contains(javax.lang.model.element.Modifier.PUBLIC)) {
            context.warn(methodWrapper.element(),
                    "Factory method should be *public* " + "(Fix modifiers of the method) on %s",
                    methodWrapper.getSimpleName());
            res = false;
        }

        if (methodWrapper.element().getModifiers().contains(Modifier.STATIC)) {
            context.warn(methodWrapper.element(),
                    "Factory method can not be *static* " + "(Fix modifiers of the method) on %s",
                    methodWrapper.getSimpleName());
            res = false;
        }

        if (!methodWrapper.isFactory()) {
            context.warn(methodWrapper.element(),
                    "Factory method should have a return type and a Class<T> targetType or" + " no parameters"
                            + "(Fix method signature) on %s",
                    methodWrapper.getSimpleName());
            res = false;
        }

        return res;
    }

    private String buildFactoryFieldName(TypeElement element) {
        return String.format(FACTORY_FIELD_TPL, element.getSimpleName());
    }

    /**
     * Generates the code to declare custom factory fields, setter and call default constructor in mapper constructor
     *
     * @param writer
     * @param assign
     * @throws IOException
     */
    public void emitFactoryFields(JavaWriter writer, boolean assign) throws IOException {
        for (TypeElement factoryField : factoryFields) {
            final String field = String.format(FACTORY_FIELD_TPL, factoryField.getSimpleName().toString());
            if (assign) {
                if (factoryField.getKind() != ElementKind.INTERFACE) {
                    TypeConstructorWrapper constructorWrapper = new TypeConstructorWrapper(context, factoryField);
                    // assign the customMapper field to a newly created instance passing to it the declared source params
                    writer.emitStatement("this.%s = new %s(%s)", field, factoryField.getQualifiedName().toString(),
                            constructorWrapper.hasMatchingSourcesConstructor ? context.newParams() : "");
                }
            } else {
                writer.emitEmptyLine();
                writer.emitJavadoc("This field is used for custom Mapping");
                if (ioC == IoC.SPRING) {
                    writer.emitAnnotation("org.springframework.beans.factory.annotation.Autowired");
                }
                writer.emitField(factoryField.asType().toString(),
                        String.format(FACTORY_FIELD_TPL, factoryField.getSimpleName().toString()),
                        EnumSet.of(PRIVATE));

                writer.emitEmptyLine();
                writer.emitJavadoc("Factory setter for " + field);

                EnumSet<Modifier> modifiers = EnumSet.of(PUBLIC, FINAL);
                if (ioC == IoC.CDI) {
                    modifiers = EnumSet.of(PUBLIC);
                }
                writer.beginMethod("void", "setFactory" + factoryField.getSimpleName(), modifiers,
                        factoryField.asType().toString(), "_factory");
                writer.emitStatement("this.%s = _factory", field);
                writer.endMethod();
                writer.emitEmptyLine();
            }
        }
    }

    public void reportUnused() {
        for (String field : unusedFactories.values()) {
            context.warn(annotatedElement, "Factory method \"%s\" is never used", field);
        }
    }

    /**
     * Called to search for a matching factory and return the corresponding MappingSourceNode
     *
     * @param inOutType      the type association we are currently mapping
     * @param outBeanWrapper The Bean wrapper of the bean we want to build
     * @return Null if no factory matches was found or the corresponding MappingSourceNode
     */
    public MappingSourceNode generateNewInstanceSourceNodes(InOutType inOutType, BeanWrapper outBeanWrapper) {

        TypeMirror out = inOutType.out();
        for (Factory factory : staticFactories) {
            if (factory.provide(out)) {
                unusedFactories.remove(factory);
                return factory.buildNewInstanceSourceNode(inOutType);
            }
        }
        Factory found = null;
        for (Factory factory : genericFactories) {
            if (factory.provide(out) && factory.isLowerClass(found)) {
                found = factory;
            }
        }
        if (found != null) {
            unusedFactories.remove(found);
            return found.buildNewInstanceSourceNode(inOutType);
        }
        return null;
    }

    public boolean hasFactory(TypeMirror typeMirror) {
        for (Factory factory : staticFactories) {
            if (factory.provide(typeMirror)) {
                return true;
            }
        }
        for (Factory factory : genericFactories) {
            if (factory.provide(typeMirror)) {
                return true;
            }
        }
        return false;
    }

    private class Factory {

        private final MethodWrapper method;
        private final String methodCall;
        private MapperGeneratorContext context;

        public Factory(MapperGeneratorContext context, MethodWrapper method, String methodCall) {
            this.context = context;
            this.method = method;
            this.methodCall = methodCall;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            Factory factory = (Factory) o;

            if (method != null ? !method.equals(factory.method) : factory.method != null)
                return false;
            return methodCall != null ? methodCall.equals(factory.methodCall) : factory.methodCall == null;

        }

        @Override
        public int hashCode() {
            int result = method != null ? method.hashCode() : 0;
            result = 31 * result + (methodCall != null ? methodCall.hashCode() : 0);
            return result;
        }

        public boolean provide(TypeMirror out) {
            if (!method.hasTypeParameter()) {
                if (context.type.isSameType(out, method.returnType())) {
                    return true;
                }
            } else if (context.type.isAssignable(out, getUpperBound())) {
                return true;
            }
            return false;
        }

        public MappingSourceNode buildNewInstanceSourceNode(InOutType inOutType) {
            if (!method.hasTypeParameter()) {

                return callStaticFactoryOut(inOutType, methodCall);
            } else {
                return callGenericFactoryOut(inOutType, methodCall);
            }
        }

        public boolean isLowerClass(Factory factory) {
            boolean res;
            if (factory == null) {
                res = true;
            } else {
                TypeMirror upperBound = getUpperBound();
                TypeMirror upperBoundCmp = factory.getUpperBound();
                res = context.type.isAssignable(upperBound, upperBoundCmp);
            }
            return res;
        }

        private TypeMirror getUpperBound() {
            return ((TypeVariable) method.returnType()).getUpperBound();
        }
    }
}