AnnotationProcessor.java
package io.github.simplejdbcmapper.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import io.github.simplejdbcmapper.annotation.Column;
import io.github.simplejdbcmapper.annotation.CreatedBy;
import io.github.simplejdbcmapper.annotation.CreatedOn;
import io.github.simplejdbcmapper.annotation.Id;
import io.github.simplejdbcmapper.annotation.Table;
import io.github.simplejdbcmapper.annotation.UpdatedBy;
import io.github.simplejdbcmapper.annotation.UpdatedOn;
import io.github.simplejdbcmapper.annotation.Version;
import io.github.simplejdbcmapper.exception.AnnotationException;
class AnnotationProcessor {
Table getTableAnnotation(Class<?> entityType) {
Table tableAnnotation = AnnotationUtils.findAnnotation(entityType, Table.class);
validateTableAnnotation(tableAnnotation, entityType);
return tableAnnotation;
}
void processColumnAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
Column colAnnotation = AnnotationUtils.findAnnotation(field, Column.class);
if (colAnnotation != null) {
String propertyName = field.getName();
String colName = colAnnotation.name();
if ("[DEFAULT]".equals(colName)) {
colName = InternalUtils.toUnderscoreName(propertyName);
}
colName = InternalUtils.toLowerCase(colName);
Integer sqlType = InternalUtils.javaTypeToSqlParameterType(field.getType());
PropertyMapping propertyMapping = null;
if (colAnnotation.sqlType() != Integer.MIN_VALUE) {
// sqlType has been configured in @Column
propertyMapping = new PropertyMapping(propertyName, field.getType(), colName, sqlType,
colAnnotation.sqlType());
} else {
propertyMapping = new PropertyMapping(propertyName, field.getType(), colName, sqlType);
}
propNameToPropertyMapping.put(propertyName, propertyMapping);
}
}
void processIdAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
processAnnotation(Id.class, field, propNameToPropertyMapping);
}
void processVersionAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
processAnnotation(Version.class, field, propNameToPropertyMapping);
}
void processCreatedOnAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
processAnnotation(CreatedOn.class, field, propNameToPropertyMapping);
}
void processUpdatedOnAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
processAnnotation(UpdatedOn.class, field, propNameToPropertyMapping);
}
void processCreatedByAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
processAnnotation(CreatedBy.class, field, propNameToPropertyMapping);
}
void processUpdatedByAnnotation(Field field, Map<String, PropertyMapping> propNameToPropertyMapping) {
processAnnotation(UpdatedBy.class, field, propNameToPropertyMapping);
}
void validateAnnotations(List<PropertyMapping> propertyMappings, Class<?> type) {
annotationDuplicateCheck(propertyMappings, type);
annotationConflictCheck(propertyMappings, type);
annotationVersionTypeCheck(propertyMappings, type);
}
private <T extends Annotation> void processAnnotation(Class<T> annotationType, Field field,
Map<String, PropertyMapping> propNameToPropertyMapping) {
Annotation annotation = AnnotationUtils.findAnnotation(field, annotationType);
if (annotation != null) {
String propertyName = field.getName();
PropertyMapping propMapping = propNameToPropertyMapping.get(propertyName);
if (propMapping == null) { // it means there is no @Column annotation for the property
String colName = InternalUtils.toUnderscoreName(propertyName); // the default column name
Integer sqlType = InternalUtils.javaTypeToSqlParameterType(field.getType());
propMapping = new PropertyMapping(propertyName, field.getType(), colName, sqlType);
propNameToPropertyMapping.put(propertyName, propMapping);
}
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(propMapping);
// set idAnnotation, versionAnnotation, createdOnAnnotation etc on
// PropertyMapping object
bw.setPropertyValue(StringUtils.uncapitalize(annotationType.getSimpleName()) + "Annotation", true);
}
}
private void validateTableAnnotation(Table tableAnnotation, Class<?> entityType) {
if (tableAnnotation == null) {
throw new AnnotationException(
entityType.getSimpleName() + " does not have the @Table annotation. It is required");
}
if (!StringUtils.hasText(tableAnnotation.name())) {
throw new AnnotationException(
"For " + entityType.getSimpleName() + " the @Table annotation has a blank name");
}
}
private void annotationDuplicateCheck(List<PropertyMapping> propertyMappings, Class<?> entityType) {
int idCnt = 0;
int versionCnt = 0;
int createdByCnt = 0;
int createdOnCnt = 0;
int updatedOnCnt = 0;
int updatedByCnt = 0;
for (PropertyMapping propMapping : propertyMappings) {
if (propMapping.isIdAnnotation()) {
idCnt++;
}
if (propMapping.isVersionAnnotation()) {
versionCnt++;
}
if (propMapping.isCreatedOnAnnotation()) {
createdOnCnt++;
}
if (propMapping.isCreatedByAnnotation()) {
createdByCnt++;
}
if (propMapping.isUpdatedOnAnnotation()) {
updatedOnCnt++;
}
if (propMapping.isUpdatedByAnnotation()) {
updatedByCnt++;
}
}
if (idCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + " has multiple @Id annotations");
}
if (versionCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + " has multiple @Version annotations");
}
if (createdOnCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + " has multiple @CreatedOn annotations");
}
if (createdByCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + " has multiple @CreatedBy annotations");
}
if (updatedOnCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + " has multiple @UpdatedOn annotations");
}
if (updatedByCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + " has multiple @UpdatedBy annotations");
}
}
private void annotationConflictCheck(List<PropertyMapping> propertyMappings, Class<?> entityType) {
for (PropertyMapping propMapping : propertyMappings) {
int conflictCnt = 0;
if (propMapping.isIdAnnotation()) {
conflictCnt++;
}
if (propMapping.isVersionAnnotation()) {
conflictCnt++;
}
if (propMapping.isCreatedOnAnnotation()) {
conflictCnt++;
}
if (propMapping.isCreatedByAnnotation()) {
conflictCnt++;
}
if (propMapping.isUpdatedOnAnnotation()) {
conflictCnt++;
}
if (propMapping.isUpdatedByAnnotation()) {
conflictCnt++;
}
if (conflictCnt > 1) {
throw new AnnotationException(entityType.getSimpleName() + "." + propMapping.getPropertyName()
+ " has multiple annotations that conflict");
}
}
}
private void annotationVersionTypeCheck(List<PropertyMapping> propertyMappings, Class<?> entityType) {
for (PropertyMapping propMapping : propertyMappings) {
if (propMapping.isVersionAnnotation() && propMapping.getPropertyType() != Integer.class) {
throw new AnnotationException("@Version requires the type of property " + entityType.getSimpleName()
+ "." + propMapping.getPropertyName() + " to be Integer");
}
}
}
}