
info.archinnov.achilles.internals.parser.FieldInfoParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of achilles-core Show documentation
Show all versions of achilles-core Show documentation
CQL implementation for Achilles using Datastax Java driver
/*
* 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 columnTypeCode = buildColumnType(elm, fieldName, rawEntityClass);
final Tuple2 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 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 buildColumnType(VariableElement elm, String fieldName, TypeName rawEntityClass) {
final CodeBlock.Builder builder = CodeBlock.builder();
final Optional partitionKey = Optional.ofNullable(elm.getAnnotation(PartitionKey.class));
final Optional clusteringColumn = Optional.ofNullable(elm.getAnnotation(ClusteringColumn.class));
final Optional staticColumn = Optional.ofNullable(elm.getAnnotation(Static.class));
final Optional computed = Optional.ofNullable(elm.getAnnotation(Computed.class));
final Optional 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 buildColumnInfo(AnnotationTree annotationTree, VariableElement elm, String fieldName, TypeName rawEntityClass) {
final CodeBlock.Builder builder = CodeBlock.builder();
final boolean isFrozen = containsAnnotation(annotationTree, Frozen.class);
final Optional partitionKey = extractTypedMap(annotationTree, PartitionKey.class);
final Optional clusteringColumn = extractTypedMap(annotationTree, ClusteringColumn.class);
final Optional 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().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 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 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()
.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()
.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.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().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()
.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()
.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()
.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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy