info.archinnov.achilles.internals.apt.processors.meta.AchillesProcessor.java Source code

Java tutorial

Introduction

Here is the source code for info.archinnov.achilles.internals.apt.processors.meta.AchillesProcessor.java

Source

/*
 * Copyright (C) 2012-2016 DuyHai DOAN
 *
 * 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 info.archinnov.achilles.internals.apt.processors.meta;

import static info.archinnov.achilles.internals.apt.AptUtils.isAnnotationOfType;
import static info.archinnov.achilles.internals.metamodel.functions.InternalSystemFunctionRegistry.SYSTEM_FUNCTIONS;
import static info.archinnov.achilles.internals.parser.TypeUtils.*;
import static info.archinnov.achilles.internals.parser.validator.BeanValidator.validateViewsAgainstBaseTable;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.io.FileUtils;

import com.google.auto.common.MoreElements;
import com.google.auto.service.AutoService;
import com.google.common.collect.Sets;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import info.archinnov.achilles.annotations.*;
import info.archinnov.achilles.exception.AchillesException;
import info.archinnov.achilles.internals.apt.AptUtils;
import info.archinnov.achilles.internals.codegen.ManagerFactoryBuilderCodeGen;
import info.archinnov.achilles.internals.codegen.ManagerFactoryCodeGen;
import info.archinnov.achilles.internals.codegen.ManagerFactoryCodeGen.ManagersAndDSLClasses;
import info.archinnov.achilles.internals.codegen.function.FunctionParameterTypesCodeGen;
import info.archinnov.achilles.internals.codegen.function.FunctionsRegistryCodeGen;
import info.archinnov.achilles.internals.codegen.meta.EntityMetaCodeGen.EntityMetaSignature;
import info.archinnov.achilles.internals.parser.context.FunctionSignature;
import info.archinnov.achilles.internals.parser.CodecRegistryParser;
import info.archinnov.achilles.internals.parser.EntityParser;
import info.archinnov.achilles.internals.parser.FunctionParser;
import info.archinnov.achilles.internals.parser.TypeUtils;
import info.archinnov.achilles.internals.parser.context.FunctionsContext;
import info.archinnov.achilles.internals.parser.context.GlobalParsingContext;
import info.archinnov.achilles.internals.utils.CollectionsHelper;

@AutoService(Processor.class)
public class AchillesProcessor extends AbstractProcessor {

    protected AptUtils aptUtils;
    protected EntityParser entityParser;
    private boolean processed = false;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        aptUtils = new AptUtils(processingEnv.getElementUtils(), processingEnv.getTypeUtils(),
                processingEnv.getMessager(), processingEnv.getFiler());
        entityParser = new EntityParser(aptUtils);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!this.processed) {

            try {
                final GlobalParsingContext parsingContext = parseCodecRegistry(annotations, roundEnv);

                final List<EntityMetaSignature> tableAndViewSignatures = discoverAndValidateTablesAndViews(
                        annotations, roundEnv, parsingContext);

                final FunctionsContext udfContext = parseAndValidateFunctionRegistry(parsingContext, annotations,
                        roundEnv, tableAndViewSignatures);

                final TypeSpec managerFactoryBuilder = ManagerFactoryBuilderCodeGen.buildInstance();
                final ManagersAndDSLClasses managersAndDSLClasses = ManagerFactoryCodeGen.buildInstance(aptUtils,
                        tableAndViewSignatures, udfContext, parsingContext);

                aptUtils.printNote("[Achilles] Reading previously generated source files (if exist)");
                try {
                    final FileObject resource = aptUtils.filer.getResource(StandardLocation.SOURCE_OUTPUT,
                            GENERATED_PACKAGE, MANAGER_FACTORY_BUILDER_CLASS);
                    final File generatedSourceFolder = new File(resource.toUri().getRawPath()
                            .replaceAll("(.+/info/archinnov/achilles/generated/).+", "$1"));
                    aptUtils.printNote("[Achilles] Cleaning previously generated source files folder : '%s'",
                            generatedSourceFolder.getPath());
                    FileUtils.deleteDirectory(generatedSourceFolder);
                } catch (IOException ioe) {
                    aptUtils.printNote(
                            "[Achilles] No previously generated source files found, proceed to code generation");
                }

                aptUtils.printNote("[Achilles] Generating CQL compatible types (used by the application) as class");
                for (TypeSpec typeSpec : FunctionParameterTypesCodeGen.buildParameterTypesClasses(udfContext)) {
                    JavaFile.builder(FUNCTION_PACKAGE, typeSpec).build().writeTo(aptUtils.filer);
                }

                aptUtils.printNote("[Achilles] Generating SystemFunctions");
                JavaFile.builder(FUNCTION_PACKAGE, FunctionsRegistryCodeGen
                        .generateFunctionsRegistryClass(SYSTEM_FUNCTIONS_CLASS, SYSTEM_FUNCTIONS)).build()
                        .writeTo(aptUtils.filer);

                aptUtils.printNote("[Achilles] Generating FunctionsRegistry");
                JavaFile.builder(FUNCTION_PACKAGE, FunctionsRegistryCodeGen
                        .generateFunctionsRegistryClass(FUNCTIONS_REGISTRY_CLASS, udfContext.functionSignatures))
                        .build().writeTo(aptUtils.filer);

                aptUtils.printNote("[Achilles] Generating ManagerFactoryBuilder");
                JavaFile.builder(GENERATED_PACKAGE, managerFactoryBuilder).build().writeTo(aptUtils.filer);

                aptUtils.printNote("[Achilles] Generating Manager factory class");
                JavaFile.builder(GENERATED_PACKAGE, managersAndDSLClasses.managerFactoryClass).build()
                        .writeTo(aptUtils.filer);

                aptUtils.printNote("[Achilles] Generating UDT meta classes");
                for (TypeSpec typeSpec : parsingContext.udtTypes.values()) {
                    JavaFile.builder(UDT_META_PACKAGE, typeSpec).build().writeTo(aptUtils.filer);
                }

                aptUtils.printNote("[Achilles] Generating entity meta classes");
                for (EntityMetaSignature signature : tableAndViewSignatures) {
                    JavaFile.builder(ENTITY_META_PACKAGE, signature.sourceCode).build().writeTo(aptUtils.filer);
                }

                aptUtils.printNote("[Achilles] Generating manager classes");
                for (TypeSpec manager : managersAndDSLClasses.managerClasses) {
                    JavaFile.builder(MANAGER_PACKAGE, manager).build().writeTo(aptUtils.filer);
                }

                aptUtils.printNote("[Achilles] Generating DSL classes");
                for (TypeSpec dsl : managersAndDSLClasses.dslClasses) {
                    JavaFile.builder(DSL_PACKAGE, dsl).build().writeTo(aptUtils.filer);
                }
            } catch (AchillesException e) {
                e.printStackTrace();
                aptUtils.printError("Error while parsing: %s", e.getMessage(), e);
            } catch (IllegalStateException e) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                aptUtils.printError("Error while parsing: %s", sw.toString(), e);
            } catch (IOException e) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                aptUtils.printError("Fail generating source file : %s", sw.toString(), e);

            } catch (Throwable throwable) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                throwable.printStackTrace(pw);
                aptUtils.printError("Fail generating source file : %s", sw.toString(), throwable);
            } finally {
                this.processed = true;
            }

        }
        return true;
    }

    private FunctionsContext parseAndValidateFunctionRegistry(GlobalParsingContext context,
            Set<? extends TypeElement> annotations, RoundEnvironment roundEnv,
            List<EntityMetaSignature> tableAndViewSignatures) {
        final List<FunctionSignature> udfSignatures = annotations.stream()
                .filter(annotation -> isAnnotationOfType(annotation, FunctionRegistry.class))
                .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream())
                .map(MoreElements::asType)
                .flatMap(x -> FunctionParser.parseFunctionRegistryAndValidateTypes(aptUtils, x, context).stream())
                .collect(toList());

        FunctionParser.validateNoDuplicateDeclaration(aptUtils, udfSignatures);

        final Set<TypeName> functionParameterTypes = udfSignatures.stream()
                .flatMap(x -> x.sourceParameterTypes.stream().map(TypeName::box)).collect(toSet());

        final Set<TypeName> functionReturnTypes = udfSignatures.stream().map(x -> x.sourceReturnType.box())
                .collect(toSet());

        final Set<TypeName> entityColumnTargetTypes = tableAndViewSignatures.stream()
                .filter(EntityMetaSignature::isTable).flatMap(x -> x.fieldMetaSignatures.stream())
                .map(x -> x.sourceType)
                //.map(TypeUtils::mapToNativeCassandraType)
                .collect(toSet());

        return new FunctionsContext(udfSignatures, CollectionsHelper.appendAll(functionParameterTypes,
                functionReturnTypes, entityColumnTargetTypes, NATIVE_TYPES));
    }

    private List<EntityMetaSignature> discoverAndValidateTablesAndViews(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv, GlobalParsingContext parsingContext) {
        final List<TypeElement> tableTypes = annotations.stream()
                .filter(annotation -> isAnnotationOfType(annotation, Table.class))
                .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream())
                .map(MoreElements::asType).collect(toList());

        final List<TypeElement> viewTypes = annotations.stream()
                .filter(annotation -> isAnnotationOfType(annotation, MaterializedView.class))
                .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream())
                .map(MoreElements::asType).collect(toList());

        final List<TypeElement> types = CollectionsHelper.appendAll(tableTypes, viewTypes);

        validateEntityNames(types);

        final List<EntityMetaSignature> tableSignatures = tableTypes.stream()
                .map(x -> entityParser.parseEntity(x, parsingContext)).collect(toList());

        final List<EntityMetaSignature> viewSignatures = viewTypes.stream()
                .map(x -> entityParser.parseView(x, parsingContext)).collect(toList());

        final List<EntityMetaSignature> tableAndViewSignatures = CollectionsHelper.appendAll(tableSignatures,
                viewSignatures);

        validateViewsAgainstBaseTable(aptUtils, viewSignatures, tableSignatures);
        return tableAndViewSignatures;
    }

    private GlobalParsingContext parseCodecRegistry(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        final GlobalParsingContext parsingContext = new GlobalParsingContext();
        parseCodecRegistry(parsingContext, annotations, roundEnv);
        return parsingContext;
    }

    private void validateEntityNames(List<TypeElement> entityTypes) {
        Map<String, String> entities = new HashedMap();
        for (TypeElement entityType : entityTypes) {
            final String className = entityType.getSimpleName().toString();
            final String FQCN = entityType.getQualifiedName().toString();
            if (entities.containsKey(className)) {
                final String existingFQCN = entities.get(className);
                aptUtils.printError("%s and %s both share the same class name, it is forbidden by Achilles", FQCN,
                        existingFQCN);
                throw new IllegalStateException(
                        format("%s and %s both share the same class name, it is forbidden by Achilles", FQCN,
                                existingFQCN));
            } else {
                entities.put(className, FQCN);
            }
        }
    }

    private void parseCodecRegistry(GlobalParsingContext parsingContext, Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {
        final boolean hasCompileTimeCodecRegistry = annotations.stream()
                .filter(annotation -> isAnnotationOfType(annotation, CodecRegistry.class)).findFirst().isPresent();
        if (hasCompileTimeCodecRegistry) {
            aptUtils.printNote("[Achilles] Parsing compile-time codec registry");
            new CodecRegistryParser(aptUtils).parseCodecs(roundEnv, parsingContext);
        }
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Sets.newHashSet(Table.class.getCanonicalName(), MaterializedView.class.getCanonicalName(),
                CodecRegistry.class.getCanonicalName(), FunctionRegistry.class.getCanonicalName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

}