org.squashtest.tm.tools.annotation.processor.DynamicComponentProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.tools.annotation.processor.DynamicComponentProcessor.java

Source

/**
 *     This file is part of the Squashtest platform.
 *     Copyright (C) 2010 - 2016 Henix, henix.fr
 *
 *     See the NOTICE file distributed with this work for additional
 *     information regarding copyright ownership.
 *
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     this software is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.squashtest.tm.tools.annotation.processor;

import java.io.IOException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import org.apache.commons.lang3.StringUtils;

/**
 * @author Gregory Fouquet
 * 
 */
public abstract class DynamicComponentProcessor<ANNOTATION extends Annotation> extends AbstractProcessor {

    private static final String FILE_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            + "<beans xmlns=\"http://www.springframework.org/schema/beans\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
            + "  xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd\">\n\n";

    private static final String FILE_FOOTER = "</beans>\n";

    protected static final String DYNAMIC_COMPONENT_TEMPLATE = "  <bean id=\"{0}\" {5} class=\"{1}\" depends-on=\"entityManagerFactory\">\n"
            + "    <property name=\"componentType\" value=\"{2}\" />\n"
            + "    <property name=\"entityType\" value=\"{3}\" />\n"
            + "    <property name=\"lookupCustomImplementation\" value=\"{4}\" />\n" + "  </bean>\n";

    private Filer filer;
    private Messager messager;
    private List<Element> dynamicComponents = new ArrayList<Element>();

    /**
     * 
     */
    public DynamicComponentProcessor() {
        super();
    }

    @Override
    public final synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();

    }

    /**
     * @see javax.annotation.processing.AbstractProcessor#process(java.util.Set,
     *      javax.annotation.processing.RoundEnvironment)
     */
    @Override
    public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        enqueueComponents(roundEnvironment);

        if (roundEnvironment.processingOver()) {
            processComponents();
        }

        return true;
    }

    private void enqueueComponents(RoundEnvironment roundEnvironment) {
        for (Element annotated : roundEnvironment.getElementsAnnotatedWith(annotationClass())) {
            //         messager.printMessage(Kind.NOTE, "INFO Enqueued dynamic component " + annotationClass().getSimpleName(),
            //               annotated);

            if (checkTarget(annotationClass(), annotated)) {
                dynamicComponents.add(annotated);
            }
        }
    }

    /**
     * @param annotation
     * @param annotated
     * @return
     */
    private boolean checkTarget(Class<ANNOTATION> annotation, Element annotated) {
        if (!annotated.getKind().isInterface()) {
            messager.printMessage(Kind.ERROR,
                    "ERROR Only interfaces can be annotated @" + annotation.getSimpleName(), annotated);

            return false;
        }

        return true;
    }

    private void processComponents() {
        Writer writer = null;

        try {
            writer = openWriter();
            outputSpringContextFile(writer);
        } catch (IOException e) {
            messager.printMessage(Kind.WARNING,
                    "WARNING Error during processing of @" + annotationClass().getSimpleName() + " annotations");
            e.printStackTrace(); // NOSONAR : I dont want no logger
        } finally {
            if (writer != null) {
                noFailCloseFile(writer);
            }
        }

    }

    private void outputSpringContextFile(Writer writer) throws IOException {
        writer.append(FILE_HEADER);

        for (Element manager : dynamicComponents) {
            //         messager.printMessage(Kind.NOTE,
            //               "INFO Processing @" + annotationClass().getSimpleName() + ' ' + manager.getSimpleName(), manager);

            String beanDefinition = buildBeanDefinition(manager);

            writer.append(beanDefinition);
        }

        writer.append(FILE_FOOTER);
    }

    private Writer openWriter() throws IOException {
        FileObject file;
        Writer writer;
        file = filer.createResource(StandardLocation.SOURCE_OUTPUT, "spring", generatedFileName(),
                (Element[]) null);

        writer = file.openWriter();
        return writer;
    }

    private void noFailCloseFile(Writer writer) {
        try {
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace(); // NOSONAR : I dont want no logger
        }
    }

    protected abstract Class<ANNOTATION> annotationClass();

    private String buildBeanDefinition(Element component) {
        ANNOTATION definition = component.getAnnotation(annotationClass());

        CharSequence beanName = StringUtils.isBlank(beanName(definition)) ? defaultBeanName(component)
                : beanName(definition);
        CharSequence managerClass = ((TypeElement) component).getQualifiedName();
        TypeMirror entityClass = extractEntityClass(definition);

        boolean lookupCustomImplementation = lookupCustomImplementation(definition);
        String primary = primaryAttribute(definition);

        String beanDefinition = MessageFormat.format(DYNAMIC_COMPONENT_TEMPLATE, beanName, beanFactoryClass(),
                managerClass, entityClass, lookupCustomImplementation, primary);
        return beanDefinition;
    }

    protected abstract String primaryAttribute(ANNOTATION definition);

    private String defaultBeanName(Element manager) {
        return StringUtils.uncapitalize(manager.getSimpleName().toString());
    }

    private TypeMirror extractEntityClass(ANNOTATION definition) {
        // explanations for the following turd :
        // http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
        TypeMirror entityClass = null;

        try {
            entityClass(definition);
        } catch (MirroredTypeException e) {
            entityClass = e.getTypeMirror();
        }
        return entityClass;
    }

    /**
     * Should get the entity property of the given component definition. Which should puke a
     * {@link MirroredTypeException} but it's OK.
     * 
     * @param componentDefinition
     * @return
     */
    protected abstract Class<?> entityClass(ANNOTATION componentDefinition);

    /**
     * Should get the "name" property of the annotation.
     * 
     * @param componentDefinition
     * @return
     */
    protected abstract String beanName(ANNOTATION componentDefinition);

    protected abstract String beanFactoryClass();

    protected abstract String generatedFileName();

    /**
     * @param definition
     * @return whether dynamic component factory should lookup custom implementation or not.
     */
    protected abstract boolean lookupCustomImplementation(ANNOTATION definition);

    /**
     * @return the messager
     */
    protected final Messager getMessager() {
        return messager;
    }

}