Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for @ApiObjectField annotations on getters and setters #165

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
*/
@Documented
@Target(value = ElementType.FIELD)
@Target(value = { ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiObjectField {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jsondoc.core.scanner.builder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.TreeSet;

Expand All @@ -9,6 +10,7 @@
import org.jsondoc.core.pojo.ApiObjectDoc;
import org.jsondoc.core.pojo.ApiObjectFieldDoc;
import org.jsondoc.core.scanner.DefaultJSONDocScanner;
import org.jsondoc.core.util.JSONDocUtils;

public class JSONDocApiObjectDocBuilder {

Expand All @@ -17,14 +19,23 @@ public static ApiObjectDoc build(Class<?> clazz) {
ApiObjectDoc apiObjectDoc = new ApiObjectDoc();

Set<ApiObjectFieldDoc> fieldDocs = new TreeSet<ApiObjectFieldDoc>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getAnnotation(ApiObjectField.class) != null) {
ApiObjectFieldDoc fieldDoc = JSONDocApiObjectFieldDocBuilder.build(field.getAnnotation(ApiObjectField.class), field);
fieldDoc.setSupportedversions(JSONDocApiVersionDocBuilder.build(field));
fieldDocs.add(fieldDoc);
}
}

for (Field field : clazz.getDeclaredFields()) {
if (field.getAnnotation(ApiObjectField.class) != null) {
ApiObjectFieldDoc fieldDoc = JSONDocApiObjectFieldDocBuilder.build(field.getAnnotation(ApiObjectField.class), field);
fieldDoc.setSupportedversions(JSONDocApiVersionDocBuilder.build(field));
fieldDocs.add(fieldDoc);
}
}

for (Method method : clazz.getDeclaredMethods()) {
if (JSONDocUtils.isFieldMethod(method)) {
ApiObjectFieldDoc fieldDoc = JSONDocApiObjectFieldDocBuilder.build(method.getAnnotation(ApiObjectField.class), method);
fieldDoc.setSupportedversions(JSONDocApiVersionDocBuilder.build(method));
fieldDocs.add(fieldDoc);
}
}

Class<?> c = clazz.getSuperclass();
if (c != null) {
if (c.isAnnotationPresent(ApiObject.class)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
package org.jsondoc.core.scanner.builder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import org.jsondoc.core.annotation.ApiObjectField;
import org.jsondoc.core.pojo.ApiObjectFieldDoc;
import org.jsondoc.core.scanner.DefaultJSONDocScanner;
import org.jsondoc.core.util.JSONDocHibernateValidatorProcessor;
import org.jsondoc.core.util.JSONDocType;
import org.jsondoc.core.util.JSONDocTypeBuilder;
import org.jsondoc.core.util.JSONDocUtils;

public class JSONDocApiObjectFieldDocBuilder {

public static ApiObjectFieldDoc build(ApiObjectField annotation, Field field) {
public static ApiObjectFieldDoc build(ApiObjectField annotation, Field field) {
ApiObjectFieldDoc doc = build(annotation, field.getName(), field.getType(), field.getGenericType());
JSONDocHibernateValidatorProcessor.processHibernateValidatorAnnotations(field, doc);
return doc;
}

public static ApiObjectFieldDoc build(ApiObjectField annotation, Method method) {
ApiObjectFieldDoc doc = build(annotation, JSONDocUtils.getPropertyName(method), method.getReturnType(), method.getGenericReturnType());
JSONDocHibernateValidatorProcessor.processHibernateValidatorAnnotations(method, doc);
return doc;
}

private static ApiObjectFieldDoc build(ApiObjectField annotation, String name, Class<?> type, Type genericType) {
ApiObjectFieldDoc apiPojoFieldDoc = new ApiObjectFieldDoc();
if (!annotation.name().trim().isEmpty()) {
apiPojoFieldDoc.setName(annotation.name());
} else {
apiPojoFieldDoc.setName(field.getName());
apiPojoFieldDoc.setName(name);
}
apiPojoFieldDoc.setDescription(annotation.description());
apiPojoFieldDoc.setJsondocType(JSONDocTypeBuilder.build(new JSONDocType(), field.getType(), field.getGenericType()));
apiPojoFieldDoc.setJsondocType(JSONDocTypeBuilder.build(new JSONDocType(), type, genericType));
// if allowedvalues property is populated on an enum field, then the enum values are overridden with the allowedvalues ones
if (field.getType().isEnum() && annotation.allowedvalues().length == 0) {
apiPojoFieldDoc.setAllowedvalues(DefaultJSONDocScanner.enumConstantsToStringArray(field.getType().getEnumConstants()));
if (type.isEnum() && annotation.allowedvalues().length == 0) {
apiPojoFieldDoc.setAllowedvalues(DefaultJSONDocScanner.enumConstantsToStringArray(type.getEnumConstants()));
} else {
apiPojoFieldDoc.setAllowedvalues(annotation.allowedvalues());
}
Expand All @@ -33,8 +48,6 @@ public static ApiObjectFieldDoc build(ApiObjectField annotation, Field field) {
apiPojoFieldDoc.addFormat(annotation.format());
}

JSONDocHibernateValidatorProcessor.processHibernateValidatorAnnotations(field, apiPojoFieldDoc);

return apiPojoFieldDoc;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,62 @@
package org.jsondoc.core.util;

import java.lang.reflect.Field;
import java.lang.reflect.Type;

import org.jsondoc.core.annotation.ApiObjectField;

public class JSONDocFieldWrapper implements Comparable<JSONDocFieldWrapper> {
private Field field;
private Integer order;
private String name;
private Class<?> type;
private Type genericType;
private ApiObjectField apiObjectFieldAnnotation;

private Integer order;

public JSONDocFieldWrapper(Field field, Integer order) {
this.field = field;
public JSONDocFieldWrapper(
String name,
Class<?> type,
Type genericType,
ApiObjectField apiObjectFieldAnnotation,
Integer order) {

this.name = name;
this.type = type;
this.genericType = genericType;
this.apiObjectFieldAnnotation = apiObjectFieldAnnotation;
this.order = order;
}

public Field getField() {
return field;
}
public String getName() {
return name;
}

public void setField(Field field) {
this.field = field;
}
public void setName(String name) {
this.name = name;
}

public Class<?> getType() {
return type;
}

public void setType(Class<?> type) {
this.type = type;
}

public Type getGenericType() {
return genericType;
}

public void setGenericType(Type genericType) {
this.genericType = genericType;
}

public ApiObjectField getApiObjectFieldAnnotation() {
return apiObjectFieldAnnotation;
}

public void setApiObjectFieldAnnotation(ApiObjectField apiObjectFieldAnnotation) {
this.apiObjectFieldAnnotation = apiObjectFieldAnnotation;
}

public Integer getOrder() {
return order;
Expand All @@ -33,7 +72,7 @@ public void setOrder(Integer order) {
@Override
public int compareTo(JSONDocFieldWrapper o) {
if(this.getOrder().equals(o.getOrder())) {
return this.getField().getName().compareTo(o.getField().getName());
return this.getName().compareTo(o.getName());
} else {
return this.getOrder() - o.getOrder();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jsondoc.core.util;

import java.lang.reflect.Field;
import java.lang.reflect.AnnotatedElement;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
Expand Down Expand Up @@ -49,7 +49,7 @@ public class JSONDocHibernateValidatorProcessor {
private final static String CreditCardNumber_message = "must be a valid credit card number";
private final static String ScriptAssert_message = "script expression %s didn't evaluate to true";

public static void processHibernateValidatorAnnotations(Field field, ApiObjectFieldDoc apiPojoFieldDoc) {
public static void processHibernateValidatorAnnotations(AnnotatedElement field, ApiObjectFieldDoc apiPojoFieldDoc) {
try {
Class.forName("org.hibernate.validator.constraints.NotBlank");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jsondoc.core.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -40,21 +41,20 @@ public static JSONDocTemplate build(Class<?> clazz, Set<Class<?>> jsondocObjects
try {
Set<JSONDocFieldWrapper> fields = getAllDeclaredFields(clazz);

for (JSONDocFieldWrapper jsondocFieldWrapper : fields) {
Field field = jsondocFieldWrapper.getField();
String fieldName = field.getName();
ApiObjectField apiObjectField = field.getAnnotation(ApiObjectField.class);
for (JSONDocFieldWrapper wrapper : fields) {
String fieldName = wrapper.getName();
ApiObjectField apiObjectField = wrapper.getApiObjectFieldAnnotation();
if (apiObjectField != null && !apiObjectField.name().isEmpty()) {
fieldName = apiObjectField.name();
}

Object value;
// This condition is to avoid StackOverflow in case class "A"
// contains a field of type "A"
if (field.getType().equals(clazz) || (apiObjectField != null && !apiObjectField.processtemplate())) {
value = getValue(Object.class, field.getGenericType(), fieldName, jsondocObjects);
if (wrapper.getType().equals(clazz) || (apiObjectField != null && !apiObjectField.processtemplate())) {
value = getValue(Object.class, wrapper.getGenericType(), fieldName, jsondocObjects);
} else {
value = getValue(field.getType(), field.getGenericType(), fieldName, jsondocObjects);
value = getValue(wrapper.getType(), wrapper.getGenericType(), fieldName, jsondocObjects);
}

jsonDocTemplate.put(fieldName, value);
Expand Down Expand Up @@ -107,20 +107,38 @@ private static Set<JSONDocFieldWrapper> getAllDeclaredFields(Class<?> clazz) {
for (Field field : declaredFields) {
if (field.isAnnotationPresent(ApiObjectField.class)) {
ApiObjectField annotation = field.getAnnotation(ApiObjectField.class);
fields.add(new JSONDocFieldWrapper(field, annotation.order()));
fields.add(new JSONDocFieldWrapper(field.getName(), field.getType(), field.getGenericType(), annotation, annotation.order()));
} else {
fields.add(new JSONDocFieldWrapper(field, Integer.MAX_VALUE));
fields.add(new JSONDocFieldWrapper(field.getName(), field.getType(), field.getGenericType(), null, Integer.MAX_VALUE));
}
}

if (clazz.getSuperclass() != null) {
List<Method> declaredMethods = new ArrayList<Method>();
if (clazz.isEnum()) {
return fields;
} else {
declaredMethods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
}

for (Method method : declaredMethods) {
if (JSONDocUtils.isFieldMethod(method)) {
if (method.isAnnotationPresent(ApiObjectField.class)) {
ApiObjectField annotation = method.getAnnotation(ApiObjectField.class);
fields.add(new JSONDocFieldWrapper(JSONDocUtils.getPropertyName(method), method.getReturnType(), method.getGenericReturnType(), annotation, annotation.order()));
} else {
fields.add(new JSONDocFieldWrapper(JSONDocUtils.getPropertyName(method), method.getReturnType(), method.getGenericReturnType(), null, Integer.MAX_VALUE));
}
}
}

if (clazz.getSuperclass() != null && !clazz.getSuperclass().isAssignableFrom(Object.class)) {
fields.addAll(getAllDeclaredFields(clazz.getSuperclass()));
}

return fields;
return fields;
}

@SuppressWarnings("unchecked")
@SuppressWarnings("unchecked")
private static <T> Class<T> wrap(Class<T> clazz) {
return clazz.isPrimitive() ? (Class<T>) primitives.get(clazz) : clazz;
}
Expand Down
14 changes: 14 additions & 0 deletions jsondoc-core/src/main/java/org/jsondoc/core/util/JSONDocUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.jsondoc.core.annotation.ApiObjectField;

public class JSONDocUtils {

public static Integer getIndexOfParameterWithAnnotation(Method method, Class<?> a) {
Expand All @@ -16,5 +18,17 @@ public static Integer getIndexOfParameterWithAnnotation(Method method, Class<?>
}
return -1;
}

public static boolean isFieldMethod(Method method) {
return (method.getAnnotation(ApiObjectField.class) != null);
}

public static String getPropertyName(Method method) {
String name = method.getName();
if ((name.startsWith("get") || name.startsWith("set")) && name.length() > 3) {
return name.substring(3, 4).toLowerCase() + name.substring(4);
} else {
return name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ public Set<Class<?>> jsondocObjects(List<String> packages) {
for (Field field : clazz.getDeclaredFields()) {
subCandidates.addAll(buildJSONDocObjectsCandidates(subCandidates, field.getType(), field.getGenericType()));
}
for (Method method : clazz.getDeclaredMethods()) {
if (JSONDocUtils.isFieldMethod(method)) {
subCandidates.addAll(buildJSONDocObjectsCandidates(subCandidates, method.getReturnType(), method.getGenericReturnType()));
}
}
}
candidates.addAll(subCandidates);

Expand Down Expand Up @@ -239,6 +244,9 @@ public ApiObjectDoc initApiObjectDoc(Class<?> clazz) {
public ApiObjectDoc mergeApiObjectDoc(Class<?> clazz, ApiObjectDoc apiObjectDoc) {
if(clazz.isAnnotationPresent(ApiObject.class)) {
ApiObjectDoc jsondocApiObjectDoc = JSONDocApiObjectDocBuilder.build(clazz);
if (jsondocApiObjectDoc.getFields() != null) {
jsondocApiObjectDoc.getFields().addAll(apiObjectDoc.getFields());
}
BeanUtils.copyProperties(jsondocApiObjectDoc, apiObjectDoc);
}
return apiObjectDoc;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jsondoc.springmvc.scanner.builder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.TreeSet;
Expand All @@ -11,6 +12,7 @@
import org.jsondoc.core.util.JSONDocHibernateValidatorProcessor;
import org.jsondoc.core.util.JSONDocType;
import org.jsondoc.core.util.JSONDocTypeBuilder;
import org.jsondoc.core.util.JSONDocUtils;

public class SpringObjectBuilder {

Expand All @@ -32,8 +34,22 @@ public static ApiObjectDoc buildObject(Class<?> clazz) {
fieldDocs.add(fieldDoc);
}

Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
for (Method method : clazz.getDeclaredMethods()) {
if (JSONDocUtils.isFieldMethod(method)) {
ApiObjectFieldDoc fieldDoc = new ApiObjectFieldDoc();
fieldDoc.setName(JSONDocUtils.getPropertyName(method));
fieldDoc.setOrder(Integer.MAX_VALUE);
fieldDoc.setRequired(DefaultJSONDocScanner.UNDEFINED.toUpperCase());
fieldDoc.setJsondocType(JSONDocTypeBuilder.build(new JSONDocType(), method.getReturnType(), method.getGenericReturnType()));

JSONDocHibernateValidatorProcessor.processHibernateValidatorAnnotations(method, fieldDoc);

fieldDocs.add(fieldDoc);
}
}

Class<?> superclass = clazz.getSuperclass();
if (superclass != null && !superclass.isAssignableFrom(Object.class) && !superclass.isEnum()) {
ApiObjectDoc parentObjectDoc = buildObject(superclass);
fieldDocs.addAll(parentObjectDoc.getFields());
}
Expand Down
Loading