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

com.michaelhradek.aurkitu.core.Processor Maven / Gradle / Ivy

There is a newer version: 0.0.7.6
Show newest version
package com.michaelhradek.aurkitu.core;

import com.michaelhradek.aurkitu.Application;
import com.michaelhradek.aurkitu.annotations.FlatBufferComment;
import com.michaelhradek.aurkitu.annotations.FlatBufferEnum;
import com.michaelhradek.aurkitu.annotations.FlatBufferEnumTypeField;
import com.michaelhradek.aurkitu.annotations.FlatBufferFieldOptions;
import com.michaelhradek.aurkitu.annotations.FlatBufferIgnore;
import com.michaelhradek.aurkitu.annotations.FlatBufferTable;
import com.michaelhradek.aurkitu.annotations.types.EnumType;
import com.michaelhradek.aurkitu.annotations.types.FieldType;
import com.michaelhradek.aurkitu.core.output.EnumDeclaration;
import com.michaelhradek.aurkitu.core.output.Schema;
import com.michaelhradek.aurkitu.core.output.TypeDeclaration;
import com.michaelhradek.aurkitu.core.output.TypeDeclaration.Property;
import com.michaelhradek.aurkitu.core.output.TypeDeclaration.Property.PropertyOptionKey;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import lombok.Getter;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

/**
 * @author m.hradek
 *
 */
@Getter
public class Processor {

    private List> sourceAnnotations;
    private Set> targetClasses;
    private ArtifactReference artifactReference;
    private Set warnedTypeNames;
    private Map namespaceOverrideMap;
    private Schema schema;

    public Processor() {
        sourceAnnotations = new ArrayList>();
        targetClasses = new HashSet>();
        warnedTypeNames = new HashSet();

        // This could be null as the value via Application could be overriden here
        namespaceOverrideMap = new HashMap();
    }

    /**
     * @param targetAnnotation Add Aurkitu annotation to process.
     * @return an instance of the Processor object
     */
    public Processor withSourceAnnotation(Class targetAnnotation) {
        sourceAnnotations.add(targetAnnotation);
        return this;
    }

    /**
     *
     * @param artifactReference The ArtifactReference component
     * @return an instance of the Processor object
     */
    public Processor withArtifactReference(ArtifactReference artifactReference) {
        this.artifactReference = artifactReference;
        return this;
    }

    /**
     * By setting this we override namespaces found while building TypeDeclaration
     *
     * @param namespaceOverrideMap The namespace map
     * @return an instance of the Processor object
     */
    public Processor withNamespaceOverrideMap(Map namespaceOverrideMap) {
        if (namespaceOverrideMap == null) {
            return this;
        }

        // Formatting the input so it is consistent
        Map temp = new HashMap();
        for (Entry item : namespaceOverrideMap.entrySet()) {
            Application.getLogger().debug(String.format("Reviewing namespaceOverrideMap item key: %s, value %s",
                item.getKey(), item.getValue()));

            temp.put(item.getKey().endsWith(".") ? item.getKey() : item.getKey() + ".",
                item.getValue().endsWith(".") ? item.getValue() : item.getValue() + ".");
        }

        this.namespaceOverrideMap = temp;
        return this;
    }

    /**
     *
     * @return a completed schema
     * @throws MojoExecutionException when there is a MalformedURLException in the classpathElements
     */
    public Schema buildSchema() throws MojoExecutionException {
        schema = new Schema();

        for (Class source : sourceAnnotations) {
            if (artifactReference == null || artifactReference.getMavenProject() == null) {
                Application.getLogger().debug("MavenProject is null; falling back to built in class scanner");
                targetClasses.addAll(AnnotationParser.findAnnotatedClasses(source));
            } else {
                targetClasses.addAll(AnnotationParser.findAnnotatedClasses(artifactReference, source));
            }
        }

        int rootTypeCount = 0;
        for (Class clazz : targetClasses) {
            if (isEnumWorkaround(clazz)) {
                schema.addEnumDeclaration(buildEnumDeclaration(clazz));
                continue;
            }

            if (clazz instanceof Class) {
                TypeDeclaration temp = buildTypeDeclaration(clazz);
                if (temp.isRoot()) {
                    Application.getLogger().debug("  Found root: " + temp.getName());
                    rootTypeCount++;
                    if (rootTypeCount > 1) {
                        throw new IllegalArgumentException("Only one rootType declaration is allowed");
                    }

                    schema.setRootType(temp.getName());
                }

                schema.addTypeDeclaration(temp);

                // Now examine inner classes
                Class[] innerClasses = clazz.getDeclaredClasses();
                for (Class inner : innerClasses) {
                    Application.getLogger().debug("  Processing inner class: " + inner.getSimpleName());
                    if (inner.isSynthetic()) {
                        Application.getLogger().debug("  Found synthetic...");
                        continue;
                    }

                    if (isEnumWorkaround(inner)) {
                        Application.getLogger().debug("  Found enum...");
                        schema.addEnumDeclaration(buildEnumDeclaration(inner));
                        continue;
                    }

                    Application.getLogger().debug("  Found type...");
                    // Inner classes cannot be root type
                    schema.addTypeDeclaration(buildTypeDeclaration(inner));
                }
            }
        }

        return schema;
    }

    /**
     *
     * @param enumClass Class to test if it is an Enum
     * @return boolean
     */
    private boolean isEnumWorkaround(Class enumClass) {
        if (enumClass.isAnonymousClass()) {
            enumClass = enumClass.getSuperclass();
        }

        return enumClass.isEnum();
    }

    /**
     * @param clazz Class which is being considered for an EnumDeclaration
     * @return an EnumDeclaration
     */
    EnumDeclaration buildEnumDeclaration(Class clazz) {
        Application.getLogger().debug("Building Enum: " + clazz.getName());

        EnumDeclaration enumD = new EnumDeclaration();
        enumD.setName(clazz.getSimpleName());

        Annotation annotation = clazz.getAnnotation(FlatBufferEnum.class);
        if (annotation instanceof FlatBufferEnum) {
            FlatBufferEnum myFlatBufferEnum = (FlatBufferEnum) annotation;
            Application.getLogger().debug("Enum structure: " + myFlatBufferEnum.value());
            enumD.setStructure(myFlatBufferEnum.value());
            Application.getLogger().debug("Enum type: " + myFlatBufferEnum.enumType());
            enumD.setType(myFlatBufferEnum.enumType());
        } else {
            Application.getLogger().debug("Not FlatBufferEnum (likely inner class); Generic enum created");
        }

        annotation = clazz.getAnnotation(FlatBufferComment.class);
        if (annotation != null) {
            String comment = ((FlatBufferComment) annotation).comment();
            if (!comment.isEmpty()) {
                Application.getLogger().debug("Found a comment assign to enum: " + comment);
                enumD.setComment(comment);
            }
        }

        Field[] fields = clazz.getDeclaredFields();

        // Find what field was annotated as the value we need to use for the declared type
        boolean setValues = false;
        Field valueField = null;
        int numAnnotations = 0;
        if (fields != null && fields.length > 0) {
            Application.getLogger().debug("Enum with declared fields detected");
            for (Field field : fields) {
                Application.getLogger().debug("  Field: " + field.getName() + " type:" + field.getType().getSimpleName());
                if (field.getAnnotation(FlatBufferEnumTypeField.class) != null) {
                    Application.getLogger().debug("    Annotated field");

                    // Verify the declaration on the enum matches the declaration of the field
                    if (enumD.getType() == null) {
                        throw new IllegalArgumentException(
                            "Missing @FlatBufferEnum(enumType = EnumType.