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; } }