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

Java tutorial

Introduction

Here is the source code for info.archinnov.achilles.internals.parser.FieldInfoParser.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 com.datastax.driver.core.ClusteringOrder.ASC;
import static com.datastax.driver.core.ClusteringOrder.DESC;
import static info.archinnov.achilles.internals.apt.AptUtils.*;
import static info.archinnov.achilles.internals.metamodel.columns.ColumnType.*;
import static info.archinnov.achilles.internals.parser.TypeUtils.*;
import static info.archinnov.achilles.internals.parser.validator.FieldValidator.validateAllowedFrozen;
import static info.archinnov.achilles.internals.parser.validator.FieldValidator.validateCompatibleColumnAnnotationsOnField;
import static java.util.Arrays.asList;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import com.datastax.driver.core.ClusteringOrder;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.TypeName;

import info.archinnov.achilles.annotations.*;
import info.archinnov.achilles.internals.apt.AptUtils;
import info.archinnov.achilles.internals.metamodel.columns.*;
import info.archinnov.achilles.internals.metamodel.index.IndexType;
import info.archinnov.achilles.internals.parser.context.EntityParsingContext;
import info.archinnov.achilles.internals.parser.context.FieldInfoContext;
import info.archinnov.achilles.internals.parser.context.IndexInfoContext;
import info.archinnov.achilles.type.TypedMap;
import info.archinnov.achilles.type.tuples.Tuple2;

public class FieldInfoParser {

    private final AptUtils aptUtils;

    public FieldInfoParser(AptUtils aptUtils) {
        this.aptUtils = aptUtils;
    }

    public FieldInfoContext buildFieldInfo(VariableElement elm, AnnotationTree annotationTree,
            EntityParsingContext context) {
        final TypeElement classElm = context.entityTypeElement;
        final TypeName rawEntityClass = TypeName.get(aptUtils.erasure(classElm));
        final TypeName currentType = TypeName.get(elm.asType()).box();
        final String fieldName = elm.getSimpleName().toString();
        final String cqlColumn = ofNullable(elm.getAnnotation(Column.class))
                .map(x -> x.value().isEmpty() ? null : x.value()).orElse(context.namingStrategy.apply(fieldName));

        final ExecutableElement getter = aptUtils.findGetter(classElm, elm, deriveGetterName(elm));
        final ExecutableElement setter = aptUtils.findSetter(classElm, elm, deriveSetterName(elm));

        final Tuple2<CodeBlock, ColumnType> columnTypeCode = buildColumnType(elm, fieldName, rawEntityClass);
        final Tuple2<CodeBlock, ColumnInfo> columnInfoCode = buildColumnInfo(annotationTree, elm, fieldName,
                rawEntityClass);
        final CodeBlock indexInfoCode = buildIndexInfo(annotationTree, elm, context);

        CodeBlock getterLambda = CodeBlock.builder()
                .add("($T entity$$) -> entity$$.$L()", rawEntityClass, getter.getSimpleName().toString()).build();

        CodeBlock setterLambda = CodeBlock.builder().add("($T entity$$, $T value$$) -> entity$$.$L(value$$)",
                rawEntityClass, currentType, setter.getSimpleName().toString()).build();

        return new FieldInfoContext(CodeBlock.builder()
                .add("new $T<>($L, $L, $S, $S, $L, $L, $L)", FIELD_INFO, getterLambda, setterLambda, fieldName,
                        cqlColumn, columnTypeCode._1(), columnInfoCode._1(), indexInfoCode)
                .build(), fieldName, cqlColumn, columnTypeCode._2(), columnInfoCode._2());
    }

    protected List<String> deriveGetterName(VariableElement elm) {
        final String fieldName = elm.getSimpleName().toString();
        final TypeMirror typeMirror = elm.asType();
        String camelCase = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        if (typeMirror.getKind() == TypeKind.BOOLEAN) {
            return asList("is" + camelCase, "get" + camelCase);
        } else {
            return asList("get" + camelCase);
        }
    }

    protected String deriveSetterName(VariableElement elm) {
        final String fieldName = elm.getSimpleName().toString();
        String setter = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        return setter;
    }

    protected Tuple2<CodeBlock, ColumnType> buildColumnType(VariableElement elm, String fieldName,
            TypeName rawEntityClass) {
        final CodeBlock.Builder builder = CodeBlock.builder();
        final Optional<PartitionKey> partitionKey = Optional.ofNullable(elm.getAnnotation(PartitionKey.class));
        final Optional<ClusteringColumn> clusteringColumn = Optional
                .ofNullable(elm.getAnnotation(ClusteringColumn.class));
        final Optional<Static> staticColumn = Optional.ofNullable(elm.getAnnotation(Static.class));
        final Optional<Computed> computed = Optional.ofNullable(elm.getAnnotation(Computed.class));
        final Optional<Counter> counter = Optional.ofNullable(elm.getAnnotation(Counter.class));

        validateCompatibleColumnAnnotationsOnField(aptUtils, fieldName, rawEntityClass, partitionKey,
                clusteringColumn, staticColumn, computed, counter);

        if (partitionKey.isPresent()) {
            builder.add("$T.$L", COLUMN_TYPE, PARTITION.name());
            return Tuple2.of(builder.build(), PARTITION);
        } else if (clusteringColumn.isPresent()) {
            builder.add("$T.$L", COLUMN_TYPE, CLUSTERING.name());
            return Tuple2.of(builder.build(), CLUSTERING);
        } else if (staticColumn.isPresent() && counter.isPresent()) {
            builder.add("$T.$L", COLUMN_TYPE, STATIC_COUNTER.name());
            return Tuple2.of(builder.build(), STATIC_COUNTER);
        } else if (staticColumn.isPresent()) {
            builder.add("$T.$L", COLUMN_TYPE, STATIC.name());
            return Tuple2.of(builder.build(), STATIC);
        } else if (computed.isPresent()) {
            builder.add("$T.$L", COLUMN_TYPE, COMPUTED.name());
            return Tuple2.of(builder.build(), COMPUTED);
        } else if (counter.isPresent()) {
            builder.add("$T.$L", COLUMN_TYPE, COUNTER.name());
            return Tuple2.of(builder.build(), COUNTER);
        } else {
            builder.add("$T.$L", COLUMN_TYPE, NORMAL.name());
            return Tuple2.of(builder.build(), NORMAL);
        }
    }

    protected Tuple2<CodeBlock, ColumnInfo> buildColumnInfo(AnnotationTree annotationTree, VariableElement elm,
            String fieldName, TypeName rawEntityClass) {
        final CodeBlock.Builder builder = CodeBlock.builder();
        final boolean isFrozen = containsAnnotation(annotationTree, Frozen.class);
        final Optional<TypedMap> partitionKey = extractTypedMap(annotationTree, PartitionKey.class);
        final Optional<TypedMap> clusteringColumn = extractTypedMap(annotationTree, ClusteringColumn.class);
        final Optional<TypedMap> computed = extractTypedMap(annotationTree, Computed.class);

        validateAllowedFrozen(isFrozen, aptUtils, elm, fieldName, rawEntityClass);

        if (partitionKey.isPresent()) {
            final int order = partitionKey.get().getTyped("order");
            aptUtils.validateTrue(order > 0,
                    "@PartitionKey order on field '%s' of class '%s' should be > 0, the ordering starts at 1",
                    fieldName, rawEntityClass);
            builder.add("new $T($L, $L)", PARTITION_KEY_INFO, order, isFrozen);
            return Tuple2.of(builder.build(), new PartitionKeyInfo(order, isFrozen));

        } else if (clusteringColumn.isPresent()) {
            final int order = clusteringColumn.get().getTyped("order");
            final ClusteringOrder clusteringOrder = clusteringColumn.get().<Boolean>getTyped("asc") ? ASC : DESC;
            aptUtils.validateTrue(order > 0,
                    "@ClusteringColumn order on field '%s' of class '%s' should be > 0, the ordering starts at 1",
                    fieldName, rawEntityClass);
            builder.add("new $T($L, $L, $T.$L)", CLUSTERING_COLUMN_INFO, order, isFrozen, CLUSTERING_ORDER,
                    clusteringOrder.name());
            return Tuple2.of(builder.build(), new ClusteringColumnInfo(order, isFrozen, clusteringOrder));

        } else if (computed.isPresent()) {
            final TypedMap typedMap = computed.get();
            final String function = typedMap.getTyped("function");
            final String alias = typedMap.getTyped("alias");
            final List<String> targetColumns = typedMap.getTyped("targetColumns");
            final Class<?> cqlClass = typedMap.getTyped("cqlClass");
            final ClassName className = ClassName.get(cqlClass);
            final StringJoiner joiner = new StringJoiner(",");

            for (String x : targetColumns) {
                joiner.add("\"" + x + "\"");
            }

            builder.add("new $T($S, $S, $T.asList(new String[]{$L}), $T.class)", COMPUTED_COLUMN_INFO, function,
                    alias, ARRAYS, joiner.toString(), className);
            return Tuple2.of(builder.build(), new ComputedColumnInfo(function, alias, targetColumns, cqlClass));

        } else {
            builder.add("new $T($L)", COLUMN_INFO, isFrozen);
            return Tuple2.of(builder.build(), new ColumnInfo(isFrozen));
        }
    }

    protected CodeBlock buildIndexInfo(AnnotationTree annotationTree, VariableElement elm,
            EntityParsingContext context) {
        final CodeBlock.Builder builder = CodeBlock.builder();
        final TypeMirror currentType = aptUtils.erasure(annotationTree.getCurrentType());
        final Optional<TypedMap> indexAnnot = extractTypedMap(annotationTree, Index.class);

        final Name fieldName = elm.getSimpleName();
        final Name className = enclosingClass(elm).getQualifiedName();
        final boolean isFrozen = containsAnnotation(annotationTree, Frozen.class);
        final boolean hasJSON = containsAnnotation(annotationTree, JSON.class);
        if (currentType.getKind().isPrimitive()) {
            if (indexAnnot.isPresent()) {
                final IndexInfoContext indexInfo = indexAnnot.get().<IndexInfoContext>getTyped("indexInfoContext")
                        .computeIndexName(elm, context);
                builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, IndexType.NORMAL,
                        indexInfo.indexName, indexInfo.indexClassName, indexInfo.indexOptions);
            } else {
                noIndex(builder);
            }

            return builder.build();
        }

        if (aptUtils.isAssignableFrom(List.class, currentType)
                || aptUtils.isAssignableFrom(Set.class, currentType)) {
            final AnnotationTree next = hasJSON ? annotationTree : annotationTree.next();
            if (indexAnnot.isPresent()) {
                final IndexInfoContext indexInfo = indexAnnot.get().<IndexInfoContext>getTyped("indexInfoContext")
                        .computeIndexName(elm, context);
                buildIndexForListOrSet(builder, isFrozen, indexInfo);
            } else if (containsAnnotation(next, Index.class)) {
                final TypedMap typedMap = extractTypedMap(next, Index.class).get();
                buildIndexForListOrSet(builder, isFrozen,
                        typedMap.<IndexInfoContext>getTyped("indexInfoContext").computeIndexName(elm, context));
            } else {
                noIndex(builder);
            }
        } else if (aptUtils.isAssignableFrom(Map.class, currentType)) {
            final AnnotationTree keyAnnotationTree = hasJSON ? annotationTree : annotationTree.next();
            final AnnotationTree valueAnnotationTree = hasJSON ? annotationTree : annotationTree.next().next();
            if (indexAnnot.isPresent()) {
                final IndexInfoContext indexInfo = indexAnnot.get().<IndexInfoContext>getTyped("indexInfoContext")
                        .computeIndexName(elm, context);

                if (isFrozen) {
                    IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.FULL : IndexType.CUSTOM;
                    builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                            indexInfo.indexClassName, indexInfo.indexOptions);
                } else {
                    IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.MAP_ENTRY
                            : IndexType.CUSTOM;
                    builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                            indexInfo.indexClassName, indexInfo.indexOptions);
                }
                aptUtils.validateFalse(containsAnnotation(keyAnnotationTree, Index.class),
                        "Cannot have @Index on Map AND key type in field '%s' of class '%s'", fieldName, className);
                aptUtils.validateFalse(containsAnnotation(valueAnnotationTree, Index.class),
                        "Cannot have @Index on Map AND value type in field '%s' of class '%s'", fieldName,
                        className);
            } else if (containsAnnotation(keyAnnotationTree, Index.class)) {
                aptUtils.validateFalse(containsAnnotation(valueAnnotationTree, Index.class),
                        "Cannot have @Index on Map key AND value type in field '%s' of class '%s'", fieldName,
                        className);
                IndexInfoContext indexInfo = extractTypedMap(keyAnnotationTree, Index.class).get()
                        .<IndexInfoContext>getTyped("indexInfoContext").computeIndexName(elm, context);
                IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.MAP_KEY : IndexType.CUSTOM;
                builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                        indexInfo.indexClassName, indexInfo.indexOptions);
            } else if (containsAnnotation(valueAnnotationTree, Index.class)) {
                aptUtils.validateFalse(containsAnnotation(keyAnnotationTree, Index.class),
                        "Cannot have @Index on Map key AND value type in field '%s' of class '%s'", fieldName,
                        className);
                IndexInfoContext indexInfo = extractTypedMap(valueAnnotationTree, Index.class).get()
                        .<IndexInfoContext>getTyped("indexInfoContext").computeIndexName(elm, context);
                IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.COLLECTION : IndexType.CUSTOM;
                builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                        indexInfo.indexClassName, indexInfo.indexOptions);
            } else {
                noIndex(builder);
            }
        } else if (indexAnnot.isPresent()) {
            final IndexInfoContext indexInfo = indexAnnot.get().<IndexInfoContext>getTyped("indexInfoContext")
                    .computeIndexName(elm, context);
            IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.NORMAL : IndexType.CUSTOM;
            builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                    indexInfo.indexClassName, indexInfo.indexOptions);
        } else {
            noIndex(builder);
        }
        return builder.build();
    }

    private void buildIndexForListOrSet(CodeBlock.Builder builder, boolean isFrozen, IndexInfoContext indexInfo) {
        if (isFrozen) {
            IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.FULL : IndexType.CUSTOM;
            builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                    indexInfo.indexClassName, indexInfo.indexOptions);
        } else {
            IndexType indexType = isBlank(indexInfo.indexClassName) ? IndexType.COLLECTION : IndexType.CUSTOM;
            builder.add("new $T($T.$L, $S, $S, $S)", INDEX_INFO, INDEX_TYPE, indexType, indexInfo.indexName,
                    indexInfo.indexClassName, indexInfo.indexOptions);
        }
    }

    private void noIndex(CodeBlock.Builder builder) {
        builder.add("$T.noIndex()", INDEX_INFO);
    }
}