package com.thinkbiganalytics.annotations;
/*-
* #%L
* thinkbig-commons-util
* %%
* Copyright (C) 2017 ThinkBig Analytics
* %%
* 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.
* #L%
*/
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* Walk a Class with Field annotations and create {@link AnnotatedFieldProperty} objects describing the annotation
*/
public class AnnotationFieldNameResolver {
private Set<Class> processedClasses = new HashSet<>();
private Map<Class, Set<AnnotatedFieldProperty>> classPropertyFields = new HashMap<>();
private Stack<String> stack = new Stack<>();
private String parentPrefix = "";
private Class<? extends Annotation> annotation;
public AnnotationFieldNameResolver(String parentPrefix, Class<? extends Annotation> annotation) {
this.parentPrefix = parentPrefix;
if (StringUtils.isNotBlank(this.parentPrefix) && this.parentPrefix.endsWith(".")) {
this.parentPrefix = StringUtils.substringBeforeLast(this.parentPrefix, ".");
}
this.annotation = annotation;
}
public AnnotationFieldNameResolver(Class<? extends Annotation> annotation) {
this.annotation = annotation;
}
public void afterFieldNameAdded(Class clazz, String classBeanPrefix, List<AnnotatedFieldProperty> names, Field field) {
}
/**
* obtain a description for the field.
*
* @return the description of the field or null.
*/
public String getFieldPropertyDescription(Field field) {
return null;
}
/**
* create the object describing the Field annotation setting the name based upon the class hierarchy
*
* @param clazz the class to inspect
* @param names the list of names already parsed. this will be the list the newly parsed field is added to
* @param field the field to parse for the annotation matching the {@link this#annotation} supplied
* @return the object describing the annotation
* @see this#stackAsString() for the naming strategy on hierachy names
*/
public AnnotatedFieldProperty addFieldProperty(Class clazz, List<AnnotatedFieldProperty> names, Field field) {
AnnotatedFieldProperty annotatedFieldProperty = new AnnotatedFieldProperty();
annotatedFieldProperty.setAnnotation(field.getAnnotation(annotation));
annotatedFieldProperty.setName(stackAsString() + field.getName());
annotatedFieldProperty.setField(field);
annotatedFieldProperty.setDescription(getFieldPropertyDescription(field));
names.add(annotatedFieldProperty);
afterFieldNameAdded(clazz, stackAsString(), names, field);
return annotatedFieldProperty;
}
/**
* Walk a class and obtain {@link AnnotatedFieldProperty} objects matching any fields with the {@link this#annotation} supplied
*
* @param clazz the class to inspect and parse annotations
* @return a list of objects describing the annotated fields
*/
public List<AnnotatedFieldProperty> getProperties(Class clazz) {
processedClasses.add(clazz);
classPropertyFields.put(clazz, new HashSet<AnnotatedFieldProperty>());
List<AnnotatedFieldProperty> names = new ArrayList<>();
List<Field> fields = FieldUtils.getFieldsListWithAnnotation(clazz, annotation);
List<Field> allFields = FieldUtils.getAllFieldsList(clazz);
for (Field field : fields) {
AnnotatedFieldProperty p = addFieldProperty(clazz, names, field);
classPropertyFields.get(clazz).add(p);
Class fieldType = field.getType();
if (!processedClasses.contains(fieldType)) {
names.addAll(getProperties(fieldType));
}
}
for (Field field : allFields) {
Class fieldType = field.getType();
if (!processedClasses.contains(fieldType)) {
stack.push(field.getName());
names.addAll(getProperties(fieldType));
//check to see if field is annotated with deserialize
JsonDeserialize deserialize = field.getAnnotation(JsonDeserialize.class);
if (deserialize != null) {
Class<?> deserializeClass = deserialize.as();
if (!processedClasses.contains(deserializeClass)) {
names.addAll(getProperties(deserializeClass));
}
}
stack.pop();
} else if (classPropertyFields.containsKey(fieldType)) {
stack.push(field.getName());
for (AnnotatedFieldProperty prop : classPropertyFields.get(fieldType)) {
addFieldProperty(clazz, names, prop.getField());
}
//check to see if field is annotated with deserialize
JsonDeserialize deserialize = field.getAnnotation(JsonDeserialize.class);
if (deserialize != null) {
Class<?> deserializeClass = deserialize.as();
if (classPropertyFields.containsKey(deserializeClass)) {
for (AnnotatedFieldProperty prop : classPropertyFields.get(deserializeClass)) {
addFieldProperty(clazz, names, prop.getField());
}
}
}
stack.pop();
}
}
return names;
}
/**
* create a name separated by "." based on a fields class hierarchy
*/
private String stackAsString() {
String str = "";
if (StringUtils.isNotBlank(parentPrefix)) {
str += parentPrefix + (stack.isEmpty() ? "." : "");
}
if (stack != null && !stack.isEmpty()) {
for (String item : stack) {
if (StringUtils.isNotBlank(str)) {
str += ".";
}
str += item;
}
if (StringUtils.isNotBlank(str)) {
str += ".";
}
}
return str;
}
}