in mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/entity/DefaultEntityFactory.java [87:259]
public EntityDefinition getDefinition(TypeElement processedClass) {
Set<TypeMirror> types = HierarchyScanner.resolveTypeHierarchy(processedClass, context);
Set<TypeElement> typeHierarchy = Sets.newLinkedHashSet();
for (TypeMirror type : types) {
typeHierarchy.add((TypeElement) context.getTypeUtils().asElement(type));
}
Language language = Language.detect(typeHierarchy);
Optional<PropertyStrategy> propertyStrategy = getPropertyStrategy(typeHierarchy);
GetterStyle getterStyle =
propertyStrategy.map(PropertyStrategy::getterStyle).orElse(language.defaultGetterStyle);
SetterStyle setterStyle =
propertyStrategy.map(PropertyStrategy::setterStyle).orElse(language.defaultSetterStyle);
boolean mutable =
propertyStrategy.map(PropertyStrategy::mutable).orElse(language.defaultMutable);
CqlNameGenerator cqlNameGenerator = buildCqlNameGenerator(typeHierarchy);
Set<String> transientProperties = getTransientPropertyNames(typeHierarchy);
Set<String> encounteredPropertyNames = Sets.newHashSet();
SortedMap<Integer, PropertyDefinition> partitionKey = new TreeMap<>();
SortedMap<Integer, PropertyDefinition> clusteringColumns = new TreeMap<>();
ImmutableList.Builder<PropertyDefinition> regularColumns = ImmutableList.builder();
ImmutableList.Builder<PropertyDefinition> computedValues = ImmutableList.builder();
// scan hierarchy for properties
for (TypeElement typeElement : typeHierarchy) {
for (Element child : typeElement.getEnclosedElements()) {
Set<Modifier> modifiers = child.getModifiers();
if (child.getKind() != ElementKind.METHOD
|| modifiers.contains(Modifier.STATIC)
|| modifiers.contains(Modifier.PRIVATE)) {
continue;
}
ExecutableElement getMethod = (ExecutableElement) child;
if (!getMethod.getParameters().isEmpty()) {
continue;
}
TypeMirror typeMirror = getMethod.getReturnType();
if (typeMirror.getKind() == TypeKind.VOID) {
continue;
}
String getMethodName = getMethod.getSimpleName().toString();
// Skip methods that test as false positives with the fluent getter style: toString(),
// hashCode() and a few Scala or Kotlin methods.
if (getMethodName.equals("toString")
|| getMethodName.equals("hashCode")
|| (language == Language.SCALA_CASE_CLASS
&& (getMethodName.equals("productPrefix")
|| getMethodName.equals("productArity")
|| getMethodName.equals("productIterator")
|| getMethodName.equals("productElementNames")
|| getMethodName.startsWith("copy$default$")))
|| (language == Language.KOTLIN_DATA_CLASS
&& getMethodName.matches("component[0-9]+"))) {
continue;
}
String propertyName = inferPropertyName(getMethodName, getterStyle, typeMirror);
if (propertyName == null) {
// getMethodName does not follow a known pattern => this is not a getter, skip
continue;
}
// skip properties we've already encountered.
if (encounteredPropertyNames.contains(propertyName)) {
continue;
}
String setMethodName;
if (mutable) {
setMethodName = inferSetMethodName(propertyName, setterStyle);
ExecutableElement setMethod = findSetMethod(typeHierarchy, setMethodName, typeMirror);
if (setMethod == null) {
continue; // must have both
}
} else {
setMethodName = null;
}
VariableElement field = findField(typeHierarchy, propertyName, typeMirror);
Map<Class<? extends Annotation>, Annotation> propertyAnnotations =
scanPropertyAnnotations(typeHierarchy, getMethod, field);
if (isTransient(propertyAnnotations, propertyName, transientProperties, getMethod, field)) {
continue;
}
int partitionKeyIndex = getPartitionKeyIndex(propertyAnnotations);
int clusteringColumnIndex = getClusteringColumnIndex(propertyAnnotations);
Optional<String> customCqlName = getCustomCqlName(propertyAnnotations);
Optional<String> computedFormula =
getComputedFormula(propertyAnnotations, getMethod, field);
PropertyType propertyType = PropertyType.parse(typeMirror, context);
PropertyDefinition property =
new DefaultPropertyDefinition(
propertyName,
customCqlName,
computedFormula,
getMethodName,
setMethodName,
propertyType,
cqlNameGenerator);
encounteredPropertyNames.add(propertyName);
if (partitionKeyIndex >= 0) {
PropertyDefinition previous = partitionKey.putIfAbsent(partitionKeyIndex, property);
if (previous != null) {
context
.getMessager()
.error(
getMethod,
"Duplicate partition key index: if multiple properties are annotated "
+ "with @%s, the annotation must be parameterized with an integer "
+ "indicating the position. Found duplicate index %d for %s and %s.",
PartitionKey.class.getSimpleName(),
partitionKeyIndex,
previous.getGetterName(),
property.getGetterName());
}
} else if (clusteringColumnIndex >= 0) {
PropertyDefinition previous =
clusteringColumns.putIfAbsent(clusteringColumnIndex, property);
if (previous != null) {
context
.getMessager()
.error(
getMethod,
"Duplicate clustering column index: if multiple properties are annotated "
+ "with @%s, the annotation must be parameterized with an integer "
+ "indicating the position. Found duplicate index %d for %s and %s.",
ClusteringColumn.class.getSimpleName(),
clusteringColumnIndex,
previous.getGetterName(),
property.getGetterName());
}
} else if (computedFormula.isPresent()) {
computedValues.add(property);
} else {
regularColumns.add(property);
}
}
}
if (encounteredPropertyNames.isEmpty()) {
context
.getMessager()
.error(
processedClass,
"@%s-annotated class must have at least one property defined.",
Entity.class.getSimpleName());
}
String entityName = Capitalizer.decapitalize(processedClass.getSimpleName().toString());
String defaultKeyspace = processedClass.getAnnotation(Entity.class).defaultKeyspace();
EntityDefinition entityDefinition =
new DefaultEntityDefinition(
ClassName.get(processedClass),
entityName,
defaultKeyspace.isEmpty() ? null : defaultKeyspace,
Optional.ofNullable(processedClass.getAnnotation(CqlName.class)).map(CqlName::value),
ImmutableList.copyOf(partitionKey.values()),
ImmutableList.copyOf(clusteringColumns.values()),
regularColumns.build(),
computedValues.build(),
cqlNameGenerator,
mutable);
validateConstructor(entityDefinition, processedClass);
return entityDefinition;
}