info.archinnov.achilles.internals.parser.AnnotationTree.java Source code

Java tutorial

Introduction

Here is the source code for info.archinnov.achilles.internals.parser.AnnotationTree.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.parser;

import static info.archinnov.achilles.internals.apt.AptUtils.*;
import static java.util.stream.Collectors.toList;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.*;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import org.eclipse.jdt.internal.compiler.apt.model.*;
import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
import org.eclipse.jdt.internal.compiler.impl.IntConstant;
import org.eclipse.jdt.internal.compiler.impl.StringConstant;
import org.eclipse.jdt.internal.compiler.lookup.*;

import com.google.auto.common.MoreTypes;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.SymbolMetadata;
import com.sun.tools.javac.code.TargetType;

import info.archinnov.achilles.annotations.*;
import info.archinnov.achilles.annotations.SASI.Analyzer;
import info.archinnov.achilles.annotations.SASI.IndexMode;
import info.archinnov.achilles.annotations.SASI.Normalization;
import info.archinnov.achilles.internals.apt.AptUtils;
import info.archinnov.achilles.internals.parser.context.*;
import info.archinnov.achilles.type.TypedMap;
import info.archinnov.achilles.type.tuples.*;

public class AnnotationTree {

    private Map<Class<? extends Annotation>, TypedMap> annotations = new LinkedHashMap<>();
    private TypeMirror currentType;
    private AnnotationTree next;
    private int depth = 1;

    AnnotationTree(TypeMirror currentType, Map<Class<? extends Annotation>, TypedMap> annotations,
            int currentDepth) {
        this.annotations = annotations;
        this.currentType = currentType;
        this.depth = currentDepth;
    }

    public static AnnotationTree buildFromMethodForReturnType(AptUtils aptUtils, ExecutableElement method) {
        if (isJavaCompiler(method)) {
            final TypeMirror returnType = method.getReturnType();
            final SymbolMetadata metadata = ((Symbol.MethodSymbol) method).getMetadata();
            final List<Attribute.TypeCompound> typeAttributes = metadata == null ? Arrays.asList()
                    : metadata.getTypeAttributes();
            final Map<Class<? extends Annotation>, TypedMap> annotationInfo = typeAttributes.stream().filter(
                    x -> x.getPosition().type == TargetType.METHOD_RETURN && x.getPosition().location.size() == 0)
                    .map(x -> (AnnotationMirror) x).collect(Collectors.toMap(x -> toAnnotation_Javac(aptUtils, x),
                            x -> inspectSupportedAnnotation_Javac(aptUtils, returnType, x)));

            final AnnotationTree annotationTree = new AnnotationTree(returnType, annotationInfo, 1);

            final List<? extends TypeMirror> nestedTypes = returnType.getKind() == TypeKind.DECLARED
                    ? MoreTypes.asDeclared(returnType).getTypeArguments()
                    : Arrays.asList();

            final List<Attribute.TypeCompound> nestedTypeAttributes = typeAttributes.stream().filter(
                    x -> x.getPosition().type == TargetType.METHOD_RETURN && x.getPosition().location.size() > 0)
                    .collect(toList());

            buildTree_Javac(aptUtils, annotationTree, 1, nestedTypes, nestedTypeAttributes);

            return annotationTree;

        } else if (isEclipseCompiler(method)) {
            final TypeMirror returnType = method.getReturnType();
            final List<? extends TypeMirror> nestedTypes = returnType.getKind() == TypeKind.DECLARED
                    ? MoreTypes.asDeclared(returnType).getTypeArguments()
                    : Arrays.asList();
            final TypeBinding binding = (TypeBinding) DeclaredTypeImplContainer.from((TypeMirrorImpl) returnType)
                    .getBinding();
            final List<AnnotationBinding> annotationBindings = Arrays.asList(binding.getTypeAnnotations());
            final Map<Class<? extends Annotation>, TypedMap> annotationInfo = annotationBindings.stream()
                    .filter(annotBinding -> {
                        final String annotationName = annotBinding.getAnnotationType().debugName();
                        return JSON.class.getCanonicalName().equals(annotationName)
                                || EmptyCollectionIfNull.class.getCanonicalName().equals(annotationName)
                                || Enumerated.class.getCanonicalName().equals(annotationName)
                                || Frozen.class.getCanonicalName().equals(annotationName)
                                || Computed.class.getCanonicalName().equals(annotationName)
                                || Counter.class.getCanonicalName().equals(annotationName)
                                || TimeUUID.class.getCanonicalName().equals(annotationName)
                                || ASCII.class.getCanonicalName().equals(annotationName)
                                || Codec.class.getCanonicalName().equals(annotationName)
                                || RuntimeCodec.class.getCanonicalName().equals(annotationName)
                                || Index.class.getCanonicalName().equals(annotationName)
                                || PartitionKey.class.getCanonicalName().equals(annotationName)
                                || ClusteringColumn.class.getCanonicalName().equals(annotationName);
                    }).map(x -> inspectSupportedAnnotation_Ecj(aptUtils, returnType, x))
                    .collect(Collectors.toMap(Tuple2::_1, Tuple2::_2));

            final AnnotationTree annotationTree = new AnnotationTree(returnType, annotationInfo, 1);
            List<TypeBinding> typeBindings = new ArrayList<>();
            if (binding instanceof ParameterizedTypeBinding) {
                typeBindings = Arrays.asList(((ParameterizedTypeBinding) binding).typeArguments());
            }
            buildTree_Ecj(aptUtils, annotationTree, 1, nestedTypes, typeBindings);
            return annotationTree;

        } else {
            aptUtils.printError(
                    "Unknown compiler, only standard Java compiler and Eclipse ECJ compiler are supported");
            return null;
        }
    }

    public static List<AnnotationTree> buildFromMethodForParam(AptUtils aptUtils, ExecutableElement method) {
        if (isJavaCompiler(method)) {
            final List<AnnotationTree> annotationTrees = new ArrayList<>(method.getParameters().size());
            final SymbolMetadata metadata = ((Symbol.MethodSymbol) method).getMetadata();
            final List<Attribute.TypeCompound> typeAttributes = metadata == null ? Arrays.asList()
                    : metadata.getTypeAttributes();
            final List<? extends VariableElement> parameters = method.getParameters();
            for (int i = 0; i < parameters.size(); i++) {
                final int finalI = i;
                final VariableElement parameter = parameters.get(i);
                final TypeMirror typeMirror = parameter.asType();
                final Map<Class<? extends Annotation>, TypedMap> annotationInfo = typeAttributes.stream()
                        .filter(x -> x.getPosition().parameter_index == finalI
                                && x.getPosition().location.size() == 0)
                        .map(x -> (AnnotationMirror) x)
                        .collect(Collectors.toMap(x -> toAnnotation_Javac(aptUtils, x),
                                x -> inspectSupportedAnnotation_Javac(aptUtils, typeMirror, x)));

                final AnnotationTree annotationTree = new AnnotationTree(typeMirror, annotationInfo, 1);

                final List<? extends TypeMirror> nestedTypes = typeMirror.getKind() == TypeKind.DECLARED
                        ? MoreTypes.asDeclared(typeMirror).getTypeArguments()
                        : Arrays.asList();

                final List<Attribute.TypeCompound> nestedTypeAttributes = typeAttributes.stream().filter(
                        x -> x.getPosition().parameter_index == finalI && x.getPosition().location.size() > 0)
                        .collect(toList());

                buildTree_Javac(aptUtils, annotationTree, 1, nestedTypes, nestedTypeAttributes);
                annotationTrees.add(annotationTree);
            }

            return annotationTrees;

        } else if (isEclipseCompiler(method)) {
            final List<AnnotationTree> annotationTrees = new ArrayList<>(method.getParameters().size());
            for (VariableElement varElm : method.getParameters()) {
                final TypeMirror currentType = varElm.asType();
                final List<? extends TypeMirror> nestedTypes = currentType.getKind() == TypeKind.DECLARED
                        ? MoreTypes.asDeclared(currentType).getTypeArguments()
                        : Arrays.asList();

                final TypeBinding binding = (TypeBinding) DeclaredTypeImplContainer
                        .from((TypeMirrorImpl) currentType).getBinding();
                final List<AnnotationBinding> annotationBindings = Arrays.asList(binding.getTypeAnnotations());
                final Map<Class<? extends Annotation>, TypedMap> annotationInfo = annotationBindings.stream()
                        .filter(annotBinding -> {
                            final String annotationName = annotBinding.getAnnotationType().debugName();
                            return JSON.class.getCanonicalName().equals(annotationName)
                                    || EmptyCollectionIfNull.class.getCanonicalName().equals(annotationName)
                                    || Enumerated.class.getCanonicalName().equals(annotationName)
                                    || Frozen.class.getCanonicalName().equals(annotationName)
                                    || Computed.class.getCanonicalName().equals(annotationName)
                                    || Counter.class.getCanonicalName().equals(annotationName)
                                    || TimeUUID.class.getCanonicalName().equals(annotationName)
                                    || ASCII.class.getCanonicalName().equals(annotationName)
                                    || Codec.class.getCanonicalName().equals(annotationName)
                                    || RuntimeCodec.class.getCanonicalName().equals(annotationName)
                                    || Index.class.getCanonicalName().equals(annotationName)
                                    || PartitionKey.class.getCanonicalName().equals(annotationName)
                                    || ClusteringColumn.class.getCanonicalName().equals(annotationName);
                        }).map(x -> inspectSupportedAnnotation_Ecj(aptUtils, currentType, x))
                        .collect(Collectors.toMap(Tuple2::_1, Tuple2::_2));

                final AnnotationTree annotationTree = new AnnotationTree(currentType, annotationInfo, 1);
                List<TypeBinding> typeBindings = new ArrayList<>();
                if (binding instanceof ParameterizedTypeBinding) {
                    typeBindings = Arrays.asList(((ParameterizedTypeBinding) binding).typeArguments());
                }

                buildTree_Ecj(aptUtils, annotationTree, 1, nestedTypes, typeBindings);
                annotationTrees.add(annotationTree);
            }

            return annotationTrees;

        } else {
            aptUtils.printError(
                    "Unknown compiler, only standard Java compiler and Eclipse ECJ compiler are supported");
            return null;
        }
    }

    public static AnnotationTree buildFrom(AptUtils aptUtils, GlobalParsingContext parsingContext,
            VariableElement varElm) {
        final String fieldName = varElm.getSimpleName().toString();
        final Name className = enclosingClass(varElm).getQualifiedName();
        final TypeMirror currentType = varElm.asType();

        final Frozen frozen = varElm.getAnnotation(Frozen.class);
        final JSON json = varElm.getAnnotation(JSON.class);
        final Enumerated enumerated = varElm.getAnnotation(Enumerated.class);
        final Codec codec = varElm.getAnnotation(Codec.class);
        final RuntimeCodec runtimeCodec = varElm.getAnnotation(RuntimeCodec.class);
        final Computed computed = varElm.getAnnotation(Computed.class);
        final Counter counter = varElm.getAnnotation(Counter.class);
        final TimeUUID timeUUID = varElm.getAnnotation(TimeUUID.class);
        final ASCII ascii = varElm.getAnnotation(ASCII.class);

        parsingContext.fieldValidator().validateCompatibleCodecAnnotationsOnField(aptUtils, fieldName, className,
                frozen, json, enumerated, codec, runtimeCodec, computed, counter, timeUUID, ascii);

        final List<? extends TypeMirror> nestedTypes = currentType.getKind() == TypeKind.DECLARED
                ? MoreTypes.asDeclared(currentType).getTypeArguments()
                : Arrays.asList();

        if (isEclipseCompiler(varElm)) {
            final FieldBinding binding = (FieldBinding) ((VariableElementImpl) varElm)._binding;

            final List<AnnotationBinding> annotationBindings = Arrays.asList(binding.getAnnotations());

            final Map<Class<? extends Annotation>, TypedMap> annotationInfo = annotationBindings.stream()
                    .filter(annotBinding -> {
                        final String annotationName = annotBinding.getAnnotationType().debugName();
                        return JSON.class.getCanonicalName().equals(annotationName)
                                || EmptyCollectionIfNull.class.getCanonicalName().equals(annotationName)
                                || Enumerated.class.getCanonicalName().equals(annotationName)
                                || Frozen.class.getCanonicalName().equals(annotationName)
                                || Computed.class.getCanonicalName().equals(annotationName)
                                || Counter.class.getCanonicalName().equals(annotationName)
                                || TimeUUID.class.getCanonicalName().equals(annotationName)
                                || ASCII.class.getCanonicalName().equals(annotationName)
                                || Codec.class.getCanonicalName().equals(annotationName)
                                || RuntimeCodec.class.getCanonicalName().equals(annotationName)
                                || Index.class.getCanonicalName().equals(annotationName)
                                || SASI.class.getCanonicalName().equals(annotationName)
                                || DSE_Search.class.getCanonicalName().equals(annotationName)
                                || PartitionKey.class.getCanonicalName().equals(annotationName)
                                || ClusteringColumn.class.getCanonicalName().equals(annotationName);
                    }).map(x -> inspectSupportedAnnotation_Ecj(aptUtils, currentType, x))
                    .collect(Collectors.toMap(Tuple2::_1, Tuple2::_2));

            final AnnotationTree annotationTree = new AnnotationTree(currentType, annotationInfo, 1);
            List<TypeBinding> typeBindings = new ArrayList<>();
            if (binding.type instanceof ParameterizedTypeBinding) {
                typeBindings = Arrays.asList(((ParameterizedTypeBinding) binding.type).typeArguments());
            }

            buildTree_Ecj(aptUtils, annotationTree, 1, nestedTypes, typeBindings);
            return annotationTree;
        } else if (isJavaCompiler(varElm)) {
            final Map<Class<? extends Annotation>, TypedMap> annotationInfo = varElm.getAnnotationMirrors().stream()
                    .filter(x -> areSameByClass(x, JSON.class) || areSameByClass(x, EmptyCollectionIfNull.class)
                            || areSameByClass(x, Enumerated.class) || areSameByClass(x, Frozen.class)
                            || areSameByClass(x, Computed.class) || areSameByClass(x, Counter.class)
                            || areSameByClass(x, TimeUUID.class) || areSameByClass(x, ASCII.class)
                            || areSameByClass(x, Codec.class) || areSameByClass(x, RuntimeCodec.class)
                            || areSameByClass(x, Index.class) || areSameByClass(x, SASI.class)
                            || areSameByClass(x, DSE_Search.class) || areSameByClass(x, PartitionKey.class)
                            || areSameByClass(x, ClusteringColumn.class))
                    .map(x -> (AnnotationMirror) x).collect(Collectors.toMap(x -> toAnnotation_Javac(aptUtils, x),
                            x -> inspectSupportedAnnotation_Javac(aptUtils, currentType, x)));

            final AnnotationTree annotationTree = new AnnotationTree(currentType, annotationInfo, 1);

            final SymbolMetadata metadata = ((Symbol.VarSymbol) varElm).getMetadata();

            final List<Attribute.TypeCompound> typeAttributes = metadata == null ? Arrays.asList()
                    : metadata.getTypeAttributes();
            buildTree_Javac(aptUtils, annotationTree, 1, nestedTypes, typeAttributes);
            return annotationTree;
        } else {
            aptUtils.printError(
                    "Unknown compiler, only standard Java compiler and Eclipse ECJ compiler are supported");
            return null;
        }

    }

    private static AnnotationTree buildTree_Javac(AptUtils aptUtils, AnnotationTree annotationTree, int depth,
            List<? extends TypeMirror> nestedTypes, List<Attribute.TypeCompound> typeAttributes) {

        final TypeMirror currentType = annotationTree.currentType;

        final boolean hasJson = containsAnnotation(annotationTree, JSON.class);

        if (hasJson) {
            return annotationTree;
        }

        if (isPrimitive(currentType) || isArray(currentType) || isAnEnum(currentType) || nestedTypes.size() == 0) {
            return annotationTree;
        } else if (aptUtils.isAssignableFrom(Tuple1.class, currentType)
                || aptUtils.isAssignableFrom(List.class, currentType)
                || aptUtils.isAssignableFrom(Set.class, currentType)
                || aptUtils.isAssignableFrom(java.util.Optional.class, currentType)) {

            final TypeMirror typeMirror = nestedTypes.get(0);
            final Map<Class<? extends Annotation>, TypedMap> annotationsInfo = typeAttributes.stream()
                    .filter(x -> x.getPosition().location.size() == depth)
                    .collect(Collectors.toMap(x -> toAnnotation_Javac(aptUtils, x),
                            x -> inspectSupportedAnnotation_Javac(aptUtils, currentType, x)));

            final AnnotationTree newTree = annotationTree
                    .addNext(new AnnotationTree(typeMirror, annotationsInfo, depth + 1));

            final List<Attribute.TypeCompound> newTypeAttributes = typeAttributes.stream()
                    .filter(x -> x.getPosition().location.size() != depth).collect(toList());

            return buildTree_Javac(aptUtils, newTree, depth + 1, getTypeArguments(typeMirror), newTypeAttributes);

        } else if (aptUtils.isAssignableFrom(Tuple2.class, currentType)
                || aptUtils.isAssignableFrom(Map.class, annotationTree.currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 2, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple3.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 3, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple4.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 4, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple5.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 5, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple6.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 6, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple7.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 7, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple8.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 8, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple9.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 9, nestedTypes, typeAttributes);
        } else if (aptUtils.isAssignableFrom(Tuple10.class, currentType)) {
            return buildTreeForTuple_Javac(aptUtils, annotationTree, depth, 10, nestedTypes, typeAttributes);
        } else if (aptUtils.getAnnotationOnClass(currentType, UDT.class).isPresent()) {
            return annotationTree;
        } else if (nestedTypes.size() == 0) {
            return annotationTree;
        } else {
            throw new IllegalStateException("Unknown current type : " + currentType.toString());
        }
    }

    private static AnnotationTree buildTree_Ecj(AptUtils aptUtils, AnnotationTree annotationTree, int depth,
            List<? extends TypeMirror> nestedTypes, List<TypeBinding> typeBindings) {
        final TypeMirror currentType = annotationTree.currentType;

        if (containsAnnotation(annotationTree, JSON.class)) {
            return annotationTree;
        }

        if (isPrimitive(currentType) || isArray(currentType) || isAnEnum(currentType) || nestedTypes.size() == 0) {
            return annotationTree;
        } else if (aptUtils.isAssignableFrom(Tuple1.class, currentType)
                || aptUtils.isAssignableFrom(List.class, currentType)
                || aptUtils.isAssignableFrom(Set.class, currentType)
                || aptUtils.isAssignableFrom(java.util.Optional.class, currentType)) {

            final TypeMirror typeMirror = nestedTypes.get(0);
            final TypeBinding nestedTypeBinding = typeBindings.get(0);

            final Map<Class<? extends Annotation>, TypedMap> annotationsInfo = Arrays
                    .asList(nestedTypeBinding.getTypeAnnotations()).stream()
                    .map(annotBinding -> inspectSupportedAnnotation_Ecj(aptUtils, currentType, annotBinding))
                    .collect(Collectors.toMap(pair -> pair._1(), pair -> pair._2()));
            final AnnotationTree newTree = annotationTree
                    .addNext(new AnnotationTree(typeMirror, annotationsInfo, depth + 1));
            List<TypeBinding> nestedBindings = new ArrayList<>();
            if (nestedTypeBinding instanceof ParameterizedTypeBinding) {
                nestedBindings = Arrays.asList(((ParameterizedTypeBinding) nestedTypeBinding).typeArguments());
            }
            return buildTree_Ecj(aptUtils, newTree, depth + 1, getTypeArguments(typeMirror), nestedBindings);
        } else if (aptUtils.isAssignableFrom(Tuple2.class, currentType)
                || aptUtils.isAssignableFrom(Map.class, annotationTree.currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 2, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple3.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 3, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple4.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 4, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple5.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 5, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple6.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 6, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple7.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 7, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple8.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 8, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple9.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 9, nestedTypes,
                    typeBindings);
        } else if (aptUtils.isAssignableFrom(Tuple10.class, currentType)) {
            return buildTreeForTuple_Ecj(aptUtils, currentType, annotationTree, depth, 10, nestedTypes,
                    typeBindings);
        } else if (aptUtils.getAnnotationOnClass(currentType, UDT.class).isPresent()) {
            return annotationTree;
        } else {
            throw new IllegalStateException("Unknown current type : " + currentType.toString());
        }
    }

    private static List<? extends TypeMirror> getTypeArguments(TypeMirror typeMirror) {
        if (isPrimitive(typeMirror) || isArray(typeMirror)) {
            return Collections.emptyList();
        } else {
            return MoreTypes.asDeclared(typeMirror).getTypeArguments();
        }
    }

    private static AnnotationTree buildTreeForTuple_Javac(AptUtils aptUtils, AnnotationTree annotationTree,
            int depth, int cardinality, List<? extends TypeMirror> nestedTypes,
            List<Attribute.TypeCompound> typeAttributes) {

        final List<Attribute.TypeCompound> annotations = typeAttributes.stream()
                .filter(x -> x.getPosition().location.size() == depth).collect(toList());
        final ArrayList<Attribute.TypeCompound> newTypeAttributes = new ArrayList<>(typeAttributes);
        newTypeAttributes.removeAll(annotations);

        AnnotationTree newTreeN;
        AnnotationTree recursiveTreeN = annotationTree;

        for (int i = 0; i < cardinality; i++) {
            final TypeMirror typeMirrorN = nestedTypes.get(i);
            final int j = i;
            final TypeMirror currentType = recursiveTreeN.currentType;
            final Map<Class<? extends Annotation>, TypedMap> annotsN = annotations.stream()
                    .filter(x -> x.getPosition().location.get(depth - 1).arg == j)
                    .collect(Collectors.toMap(x -> toAnnotation_Javac(aptUtils, x),
                            x -> inspectSupportedAnnotation_Javac(aptUtils, currentType, x)));

            newTreeN = recursiveTreeN.addNext(new AnnotationTree(typeMirrorN, annotsN, depth + 1));
            recursiveTreeN = buildTree_Javac(aptUtils, newTreeN, depth + 1, getTypeArguments(typeMirrorN),
                    newTypeAttributes);
        }
        return recursiveTreeN;
    }

    private static AnnotationTree buildTreeForTuple_Ecj(AptUtils aptUtils, TypeMirror currentType,
            AnnotationTree annotationTree, int depth, int cardinality, List<? extends TypeMirror> nestedTypes,
            List<TypeBinding> typeBindings) {

        AnnotationTree newTreeN;
        AnnotationTree recursiveTreeN = annotationTree;

        for (int i = 0; i < cardinality; i++) {
            final TypeMirror typeMirrorN = nestedTypes.get(i);
            final TypeBinding typeBinding = typeBindings.get(i);
            final Map<Class<? extends Annotation>, TypedMap> annotationsInfo = Arrays
                    .asList(typeBinding.getTypeAnnotations()).stream()
                    .map(annotBinding -> inspectSupportedAnnotation_Ecj(aptUtils, currentType, annotBinding))
                    .collect(Collectors.toMap(pair -> pair._1(), pair -> pair._2()));
            List<TypeBinding> nestedBindings = new ArrayList<>();
            if (typeBinding instanceof ParameterizedTypeBinding) {
                nestedBindings = Arrays.asList(((ParameterizedTypeBinding) typeBinding).typeArguments());
            }
            newTreeN = recursiveTreeN.addNext(new AnnotationTree(typeMirrorN, annotationsInfo, depth + 1));
            recursiveTreeN = buildTree_Ecj(aptUtils, newTreeN, depth + 1, getTypeArguments(typeMirrorN),
                    nestedBindings);
        }
        return recursiveTreeN;
    }

    AnnotationTree addNext(AnnotationTree next) {
        this.next = next;
        return this.next;
    }

    public boolean hasNext() {
        return this.next != null;
    }

    public AnnotationTree next() {
        if (hasNext()) {
            return next;
        } else {
            throw new IllegalStateException(
                    String.format("No more leaf for annotation tree. Current type = %s, depth = %s",
                            currentType.toString(), depth));
        }
    }

    public Map<Class<? extends Annotation>, TypedMap> getAnnotations() {
        return annotations;
    }

    public TypeMirror getCurrentType() {
        return currentType;
    }

    public int depth() {
        return this.depth;
    }

    private static TypedMap inspectSupportedAnnotation_Javac(AptUtils aptUtils, TypeMirror currentType,
            AnnotationMirror annotation) {
        final TypedMap typedMap = new TypedMap();
        if (areSameByClass(annotation, Enumerated.class)) {
            final Enumerated.Encoding encoding = getElementValueEnum(annotation, "value", Enumerated.Encoding.class,
                    true);
            return TypedMap.of("value", encoding);
        } else if (areSameByClass(annotation, Codec.class)) {
            final CodecContext codecContext = CodecFactory.buildCodecContext(aptUtils, annotation);
            return TypedMap.of("codecContext", codecContext);
        } else if (areSameByClass(annotation, RuntimeCodec.class)) {
            final RuntimeCodecContext runtimeCodecContext = CodecFactory.buildRuntimeCodecContext(currentType,
                    annotation);
            return TypedMap.of("runtimeCodecContext", runtimeCodecContext);
        } else if (areSameByClass(annotation, Computed.class)) {
            final String function = getElementValue(annotation, "function", String.class, false);
            final Optional<Class<Object>> cqlClass = getElementValueClass(annotation, "cqlClass", false);
            aptUtils.validateTrue(cqlClass.isPresent(), "Cannot find 'cqlClass' attribute value on annotation %s",
                    Computed.class.getCanonicalName());
            String alias = getElementValue(annotation, "alias", String.class, false);
            final List<String> targetColumns = getElementValueArray(annotation, "targetColumns", String.class,
                    false);
            typedMap.put("function", function);
            typedMap.put("cqlClass", cqlClass.get());
            typedMap.put("alias", alias);
            typedMap.put("targetColumns", targetColumns);
            return typedMap;
        } else if (areSameByClass(annotation, Index.class)) {
            final String indexName = getElementValue(annotation, "name", String.class, true);
            final String indexOptions = getElementValue(annotation, "indexOptions", String.class, true);
            final String indexClassName = getElementValue(annotation, "indexClassName", String.class, true);
            typedMap.put("indexInfoContext", new IndexInfoContext(indexName, indexClassName, indexOptions));
            return typedMap;
        } else if (areSameByClass(annotation, SASI.class)) {
            final String indexName = getElementValue(annotation, "name", String.class, true);
            final IndexMode indexMode = getElementValueEnum(annotation, "indexMode", IndexMode.class, true);
            final boolean analyzed = getElementValue(annotation, "analyzed", Boolean.class, true);
            final Analyzer analyzerClass = getElementValueEnum(annotation, "analyzerClass", Analyzer.class, true);
            final int maxCompactionFlushMemoryInMb = getElementValue(annotation, "maxCompactionFlushMemoryInMb",
                    Integer.class, true);
            final Normalization normalization = getElementValueEnum(annotation, "normalization",
                    Normalization.class, true);
            final String locale = getElementValue(annotation, "locale", String.class, true);
            final boolean enableStemming = getElementValue(annotation, "enableStemming", Boolean.class, true);
            final boolean skipStopWords = getElementValue(annotation, "skipStopWords", Boolean.class, true);
            typedMap.put("sasiInfoContext", new SASIInfoContext(indexName, indexMode, analyzed, analyzerClass,
                    maxCompactionFlushMemoryInMb, normalization, locale, enableStemming, skipStopWords));
            return typedMap;
        } else if (areSameByClass(annotation, DSE_Search.class)) {
            final boolean fullTextSearchEnabled = getElementValue(annotation, "fullTextSearchEnabled",
                    Boolean.class, true);
            typedMap.put("dseSearchInfoContext", new DSESearchInfoContext(fullTextSearchEnabled));
            return typedMap;
        } else if (areSameByClass(annotation, PartitionKey.class)) {
            typedMap.put("order", getElementValue(annotation, "value", Integer.class, true));
            return typedMap;
        } else if (areSameByClass(annotation, ClusteringColumn.class)) {
            typedMap.put("order", getElementValue(annotation, "value", Integer.class, true));
            typedMap.put("asc", getElementValue(annotation, "asc", Boolean.class, true));
            return typedMap;
        } else {
            return typedMap;
        }
    }

    private static Class<? extends Annotation> toAnnotation_Javac(AptUtils aptUtils,
            AnnotationMirror annotationMirror) {
        if (areSameByClass(annotationMirror, JSON.class)) {
            return JSON.class;
        } else if (areSameByClass(annotationMirror, EmptyCollectionIfNull.class)) {
            return EmptyCollectionIfNull.class;
        } else if (areSameByClass(annotationMirror, Enumerated.class)) {
            return Enumerated.class;
        } else if (areSameByClass(annotationMirror, Frozen.class)) {
            return Frozen.class;
        } else if (areSameByClass(annotationMirror, Computed.class)) {
            return Computed.class;
        } else if (areSameByClass(annotationMirror, Counter.class)) {
            return Counter.class;
        } else if (areSameByClass(annotationMirror, TimeUUID.class)) {
            return TimeUUID.class;
        } else if (areSameByClass(annotationMirror, ASCII.class)) {
            return ASCII.class;
        } else if (areSameByClass(annotationMirror, Codec.class)) {
            return Codec.class;
        } else if (areSameByClass(annotationMirror, RuntimeCodec.class)) {
            return RuntimeCodec.class;
        } else if (areSameByClass(annotationMirror, Index.class)) {
            return Index.class;
        } else if (areSameByClass(annotationMirror, SASI.class)) {
            return SASI.class;
        } else if (areSameByClass(annotationMirror, DSE_Search.class)) {
            return DSE_Search.class;
        } else if (areSameByClass(annotationMirror, PartitionKey.class)) {
            return PartitionKey.class;
        } else if (areSameByClass(annotationMirror, ClusteringColumn.class)) {
            return ClusteringColumn.class;
        } else {
            aptUtils.printError("Unsupported annotation : " + annotationMirror.toString());
            throw new IllegalArgumentException("Unsupported annotation : " + annotationMirror.toString());
        }
    }

    private static Tuple2<Class<? extends Annotation>, TypedMap> inspectSupportedAnnotation_Ecj(AptUtils aptUtils,
            TypeMirror currentType, AnnotationBinding annotationBinding) {
        final TypedMap typedMap = new TypedMap();
        final String annotationName = annotationBinding.getAnnotationType().debugName();
        if (JSON.class.getCanonicalName().equals(annotationName)) {
            return Tuple2.of(JSON.class, typedMap);
        } else if (EmptyCollectionIfNull.class.getCanonicalName().equals(annotationName)) {
            return Tuple2.of(EmptyCollectionIfNull.class, typedMap);
        } else if (Enumerated.class.getCanonicalName().equals(annotationName)) {
            final Enumerated.Encoding encoding = Arrays.asList(annotationBinding.getElementValuePairs()).stream()
                    .filter(pair -> new String(pair.getName()).equals("value")).map(pair -> pair.getValue())
                    .filter(value -> value instanceof FieldBinding).map(value -> (FieldBinding) value)
                    .filter(value -> Enumerated.Encoding.class.getCanonicalName().equals(value.type.debugName()))
                    .map(value -> Enumerated.Encoding.valueOf(Enumerated.Encoding.class, new String(value.name)))
                    .findFirst().orElse(Enumerated.Encoding.NAME);
            typedMap.put("value", encoding);
            return Tuple2.of(Enumerated.class, typedMap);
        } else if (Frozen.class.getCanonicalName().equals(annotationName)) {
            return Tuple2.of(Frozen.class, typedMap);
        } else if (Computed.class.getCanonicalName().equals(annotationName)) {
            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());
            final String functionName = ((StringConstant) pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("function")).findFirst().get().getValue())
                            .stringValue();

            final String alias = ((StringConstant) pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("alias")).findFirst().get().getValue())
                            .stringValue();

            final String cqlClassName = ((ReferenceBinding) pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("cqlClass")).findFirst().get().getValue())
                            .debugName();

            Class<?> cqlClass = null;
            try {
                cqlClass = Class.forName(cqlClassName);
            } catch (ClassNotFoundException e) {
                aptUtils.printError("Cannot find CQL class %s", cqlClassName);
            }

            final List<String> targetColumns = Arrays
                    .asList((Object[]) pairs.stream()
                            .filter(pair -> new String(pair.getName()).equals("targetColumns")).findFirst().get()
                            .getValue())
                    .stream().map(object -> (StringConstant) object)
                    .map(stringConstant -> stringConstant.stringValue()).collect(toList());

            typedMap.put("function", functionName);
            typedMap.put("alias", alias);
            typedMap.put("cqlClass", cqlClass);
            typedMap.put("targetColumns", targetColumns);
            return Tuple2.of(Computed.class, typedMap);
        } else if (Counter.class.getCanonicalName().equals(annotationName)) {
            return Tuple2.of(Counter.class, typedMap);
        } else if (TimeUUID.class.getCanonicalName().equals(annotationName)) {
            return Tuple2.of(TimeUUID.class, typedMap);
        } else if (ASCII.class.getCanonicalName().equals(annotationName)) {
            return Tuple2.of(ASCII.class, typedMap);
        } else if (Codec.class.getCanonicalName().equals(annotationName)) {
            final Optional<String> codecClassName = Arrays.asList(annotationBinding.getElementValuePairs()).stream()
                    .filter(pair -> new String(pair.getName()).equals("value")).map(pair -> pair.getValue())
                    .filter(value -> value instanceof ReferenceBinding)
                    .map(value -> ((ReferenceBinding) value).debugName()).findFirst();
            aptUtils.validateTrue(codecClassName.isPresent(), "Cannot find codec class on '%s' for type '%s",
                    Codec.class.getCanonicalName(), currentType);

            final CodecContext codecContext = CodecFactory.buildCodecContext(aptUtils, codecClassName.get());
            typedMap.put("codecContext", codecContext);
            return Tuple2.of(Codec.class, typedMap);

        } else if (RuntimeCodec.class.getCanonicalName().equals(annotationName)) {

            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());
            final Optional<String> codecName = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("codecName")).findFirst()
                    .map(x -> (StringConstant) x.getValue()).map(x -> x.stringValue());

            final String targetTypeName = ((ReferenceBinding) pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("cqlClass")).findFirst().get().getValue())
                            .debugName();

            Class<?> targetType = null;
            try {
                targetType = Class.forName(targetTypeName);
            } catch (ClassNotFoundException e) {
                aptUtils.printError("Cannot find CQL class %s", targetTypeName);
            }

            final RuntimeCodecContext runtimeCodecContext = new RuntimeCodecContext(TypeName.get(currentType),
                    TypeName.get(targetType), codecName);
            typedMap.put("runtimeCodecContext", runtimeCodecContext);
            return Tuple2.of(RuntimeCodec.class, typedMap);

        } else if (Index.class.getCanonicalName().equals(annotationName)) {
            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());
            final String name = pairs.stream().filter(pair -> new String(pair.getName()).equals("name"))
                    .map(pair -> ((StringConstant) pair.getValue()).stringValue()).findFirst().orElse("");

            final String indexClassName = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("indexClassName"))
                    .map(pair -> ((StringConstant) pair.getValue()).stringValue()).findFirst().orElse("");

            final String indexOptions = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("indexOptions"))
                    .map(pair -> ((StringConstant) pair.getValue()).stringValue()).findFirst().orElse("");

            typedMap.put("indexInfoContext", new IndexInfoContext(name, indexClassName, indexOptions));
            return Tuple2.of(Index.class, typedMap);
        } else if (SASI.class.getCanonicalName().equals(annotationName)) {

            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());

            final String indexName = pairs.stream().filter(pair -> new String(pair.getName()).equals("name"))
                    .map(pair -> ((StringConstant) pair.getValue()).stringValue()).findFirst().orElse("");

            final IndexMode indexMode = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("indexMode")).map(pair -> pair.getValue())
                    .filter(value -> value instanceof FieldBinding).map(value -> (FieldBinding) value)
                    .filter(value -> IndexMode.class.getCanonicalName().equals(value.type.debugName()))
                    .map(value -> IndexMode.valueOf(IndexMode.class, new String(value.name))).findFirst()
                    .orElse(IndexMode.PREFIX);

            final boolean analyzed = pairs.stream().filter(pair -> new String(pair.getName()).equals("analyzed"))
                    .map(pair -> ((BooleanConstant) pair.getValue()).booleanValue()).findFirst().orElse(false);

            final Analyzer analyzerClass = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("analyzerClass")).map(pair -> pair.getValue())
                    .filter(value -> value instanceof FieldBinding).map(value -> (FieldBinding) value)
                    .filter(value -> Analyzer.class.getCanonicalName().equals(value.type.debugName()))
                    .map(value -> Analyzer.valueOf(Analyzer.class, new String(value.name))).findFirst()
                    .orElse(Analyzer.NO_OP_ANALYZER);

            final int maxCompactionFlushMemoryInMb = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("maxCompactionFlushMemoryInMb"))
                    .map(pair -> ((IntConstant) pair.getValue()).intValue()).findFirst().orElse(1024);

            final Normalization normalization = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("normalization")).map(pair -> pair.getValue())
                    .filter(value -> value instanceof FieldBinding).map(value -> (FieldBinding) value)
                    .filter(value -> Normalization.class.getCanonicalName().equals(value.type.debugName()))
                    .map(value -> Analyzer.valueOf(Normalization.class, new String(value.name))).findFirst()
                    .orElse(Normalization.NONE);

            final String locale = pairs.stream().filter(pair -> new String(pair.getName()).equals("locale"))
                    .map(pair -> ((StringConstant) pair.getValue()).stringValue()).findFirst().orElse("en");

            final boolean enableStemming = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("enableStemming"))
                    .map(pair -> ((BooleanConstant) pair.getValue()).booleanValue()).findFirst().orElse(false);

            final boolean skipStopWords = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("skipStopWords"))
                    .map(pair -> ((BooleanConstant) pair.getValue()).booleanValue()).findFirst().orElse(false);

            typedMap.put("sasiInfoContext", new SASIInfoContext(indexName, indexMode, analyzed, analyzerClass,
                    maxCompactionFlushMemoryInMb, normalization, locale, enableStemming, skipStopWords));
            return Tuple2.of(SASI.class, typedMap);
        } else if (DSE_Search.class.getCanonicalName().equals(annotationName)) {
            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());
            final boolean fullTextSearchEnabled = pairs.stream()
                    .filter(pair -> new String(pair.getName()).equals("fullTextSearchEnabled"))
                    .map(pair -> ((BooleanConstant) pair.getValue()).booleanValue()).findFirst().orElse(false);
            typedMap.put("dseSearchInfoContext", new DSESearchInfoContext(fullTextSearchEnabled));
            return Tuple2.of(DSE_Search.class, typedMap);
        } else if (PartitionKey.class.getCanonicalName().equals(annotationName)) {
            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());
            final Integer order = pairs.stream().filter(pair -> new String(pair.getName()).equals("value"))
                    .map(pair -> ((IntConstant) pair.getValue()).intValue()).findFirst().orElse(1);
            typedMap.put("order", order);
            return Tuple2.of(PartitionKey.class, typedMap);
        } else if (ClusteringColumn.class.getCanonicalName().equals(annotationName)) {
            final List<ElementValuePair> pairs = Arrays.asList(annotationBinding.getElementValuePairs());
            final Integer order = pairs.stream().filter(pair -> new String(pair.getName()).equals("value"))
                    .map(pair -> ((IntConstant) pair.getValue()).intValue()).findFirst().orElse(1);
            final Boolean asc = pairs.stream().filter(pair -> new String(pair.getName()).equals("asc"))
                    .map(pair -> ((BooleanConstant) pair.getValue()).booleanValue()).findFirst().orElse(true);

            typedMap.put("order", order);
            typedMap.put("asc", asc);
            return Tuple2.of(ClusteringColumn.class, typedMap);
        } else {
            aptUtils.printError("Unsupported annotation : " + annotationName);
            throw new IllegalArgumentException("Unsupported annotation : " + annotationName.toString());
        }
    }

    public static Optional<TypeName> findOptionalViewBaseClass(AptUtils aptUtils, TypeElement elm) {
        if (AptUtils.isJavaCompiler(elm)) {
            return findOptionalViewBaseClass_Javac(aptUtils, elm);
        } else if (AptUtils.isEclipseCompiler(elm)) {
            return findOptionalViewBaseClass_Ecj(elm);
        } else {
            aptUtils.printError(
                    "Unknown compiler, only standard Java compiler and Eclipse ECJ compiler are supported");
            return Optional.empty();
        }
    }

    private static Optional<TypeName> findOptionalViewBaseClass_Javac(AptUtils aptUtils, TypeElement elm) {
        Optional<TypeName> viewBaseClass = elm.getAnnotationMirrors().stream()
                .filter(x -> areSameByClass(x, MaterializedView.class)).findFirst()
                .flatMap(view -> aptUtils.getElementValueClass(view, "baseEntity", false)).map(ClassName::get);

        if (viewBaseClass.isPresent()) {
            return viewBaseClass;
        } else {
            return elm.getAnnotationMirrors().stream().filter(x -> areSameByClass(x, MaterializedView.class))
                    .findFirst()
                    .map(view -> aptUtils.getElementValueClassName(view, "baseEntity", false).toString())
                    .map(baseClassName -> aptUtils.elementUtils.getTypeElement(baseClassName).asType())
                    .map(ClassName::get);
        }
    }

    private static Optional<TypeName> findOptionalViewBaseClass_Ecj(TypeElement elm) {
        return Arrays.asList(((TypeElementImpl) elm)._binding.getAnnotations()).stream()
                .filter(annotBinding -> MaterializedView.class.getCanonicalName()
                        .equals(annotBinding.getAnnotationType().debugName()))
                .flatMap(viewAnnot -> Arrays.asList(viewAnnot.getElementValuePairs()).stream())
                .filter(pair -> new String(pair.getName()).equals("baseEntity"))
                .filter(pair -> pair.getValue() instanceof ReferenceBinding)
                .map(pair -> (ReferenceBinding) pair.getValue()).map(x -> x.debugName()).map(ClassName::bestGuess)
                .map(x -> (TypeName) x).findFirst();
    }

    public static Optional<String> findKeyspaceForFunctionRegistry(AptUtils aptUtils,
            TypeElement functionRegistry) {
        if (isJavaCompiler(functionRegistry)) {
            return functionRegistry.getAnnotationMirrors().stream()
                    .filter(x -> areSameByClass(x, FunctionRegistry.class)).findFirst()
                    .map(annot -> aptUtils.getElementValue(annot, "keyspace", String.class, false));

        } else if (AptUtils.isEclipseCompiler(functionRegistry)) {
            return Arrays.asList(((TypeElementImpl) functionRegistry)._binding.getAnnotations()).stream()
                    .filter(annotBinding -> FunctionRegistry.class.getCanonicalName()
                            .equals(annotBinding.getAnnotationType().debugName()))
                    .flatMap(annot -> Arrays.asList(annot.getElementValuePairs()).stream())
                    .filter(pair -> new String(pair.getName()).equals("keyspace")).findFirst()
                    .map(elementValuePair -> (StringConstant) elementValuePair.getValue())
                    .map(stringConstant -> stringConstant.stringValue());
        } else {
            aptUtils.printError(
                    "Unknown compiler, only standard Java compiler and Eclipse ECJ compiler are supported");
            return Optional.empty();
        }
    }
}