From 69e1a1dafd743a83ddaa3c9f0956d9f23bb07bfb Mon Sep 17 00:00:00 2001 From: Joffrey BION Date: Fri, 7 Apr 2017 01:49:12 +0200 Subject: Fix the case of types in JsonDoc --- .../doc/builders/JSONDocApiObjectDocBuilder.java | 57 ++++++ .../builders/JSONDocApiObjectFieldDocBuilder.java | 42 +++++ .../doc/builders/JSONDocTemplateBuilder.java | 18 +- .../doc/builders/JSONDocTypeBuilder.java | 120 ++++++++++++ .../doc/builders/SpringObjectBuilder.java | 52 +++++ .../doc/builders/SpringResponseBuilder.java | 31 +++ .../doc/scanner/JsonDocWebSocketScanner.java | 107 +++-------- .../sevenwonders/doc/scanner/ObjectsScanner.java | 209 +++++++++++++++++++++ 8 files changed, 538 insertions(+), 98 deletions(-) create mode 100644 backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectDocBuilder.java create mode 100644 backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectFieldDocBuilder.java create mode 100644 backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTypeBuilder.java create mode 100644 backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringObjectBuilder.java create mode 100644 backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringResponseBuilder.java create mode 100644 backend/src/main/java/org/luxons/sevenwonders/doc/scanner/ObjectsScanner.java (limited to 'backend/src/main/java/org') diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectDocBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectDocBuilder.java new file mode 100644 index 00000000..8a59451f --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectDocBuilder.java @@ -0,0 +1,57 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.reflect.Field; +import java.util.Set; +import java.util.TreeSet; + +import org.jsondoc.core.annotation.ApiObject; +import org.jsondoc.core.annotation.ApiObjectField; +import org.jsondoc.core.pojo.ApiObjectDoc; +import org.jsondoc.core.pojo.ApiObjectFieldDoc; +import org.jsondoc.core.scanner.DefaultJSONDocScanner; +import org.jsondoc.core.scanner.builder.JSONDocApiVersionDocBuilder; + +public class JSONDocApiObjectDocBuilder { + + public static ApiObjectDoc build(Class clazz) { + ApiObject apiObject = clazz.getAnnotation(ApiObject.class); + ApiObjectDoc apiObjectDoc = new ApiObjectDoc(); + + Set fieldDocs = new TreeSet(); + 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); + } + } + + Class c = clazz.getSuperclass(); + if (c != null) { + if (c.isAnnotationPresent(ApiObject.class)) { + ApiObjectDoc objDoc = build(c); + fieldDocs.addAll(objDoc.getFields()); + } + } + + if (clazz.isEnum()) { + apiObjectDoc.setAllowedvalues(DefaultJSONDocScanner.enumConstantsToStringArray(clazz.getEnumConstants())); + } + + if (apiObject.name().trim().isEmpty()) { + apiObjectDoc.setName(clazz.getSimpleName()); + } else { + apiObjectDoc.setName(apiObject.name()); + } + + apiObjectDoc.setDescription(apiObject.description()); + apiObjectDoc.setFields(fieldDocs); + apiObjectDoc.setGroup(apiObject.group()); + apiObjectDoc.setVisibility(apiObject.visibility()); + apiObjectDoc.setStage(apiObject.stage()); + apiObjectDoc.setShow(apiObject.show()); + + return apiObjectDoc; + } +} diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectFieldDocBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectFieldDocBuilder.java new file mode 100644 index 00000000..a1c9d7c6 --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocApiObjectFieldDocBuilder.java @@ -0,0 +1,42 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.reflect.Field; + +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; + +public class JSONDocApiObjectFieldDocBuilder { + + public static ApiObjectFieldDoc build(ApiObjectField annotation, Field field) { + ApiObjectFieldDoc apiPojoFieldDoc = new ApiObjectFieldDoc(); + if (!annotation.name().trim().isEmpty()) { + apiPojoFieldDoc.setName(annotation.name()); + } else { + apiPojoFieldDoc.setName(field.getName()); + } + apiPojoFieldDoc.setDescription(annotation.description()); + apiPojoFieldDoc.setJsondocType( + JSONDocTypeBuilder.build(new JSONDocType(), field.getType(), field.getGenericType())); + // 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())); + } else { + apiPojoFieldDoc.setAllowedvalues(annotation.allowedvalues()); + } + apiPojoFieldDoc.setRequired(String.valueOf(annotation.required())); + apiPojoFieldDoc.setOrder(annotation.order()); + + if (!annotation.format().isEmpty()) { + apiPojoFieldDoc.addFormat(annotation.format()); + } + + JSONDocHibernateValidatorProcessor.processHibernateValidatorAnnotations(field, apiPojoFieldDoc); + + return apiPojoFieldDoc; + } +} diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java index 78c86584..f518f319 100644 --- a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java @@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory; public class JSONDocTemplateBuilder { private static final Logger log = LoggerFactory.getLogger(org.jsondoc.core.util.JSONDocTemplateBuilder.class); + private static final Map, Class> primitives = new HashMap, Class>(); static { @@ -38,7 +39,7 @@ public class JSONDocTemplateBuilder { public static JSONDocTemplate build(Class clazz, Set> jsondocObjects) { final JSONDocTemplate jsonDocTemplate = new JSONDocTemplate(); - if(jsondocObjects.contains(clazz)) { + if (jsondocObjects.contains(clazz)) { try { Set fields = getAllDeclaredFields(clazz); @@ -53,7 +54,8 @@ public class JSONDocTemplateBuilder { 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())) { + if (field.getType().equals(clazz) || (apiObjectField != null + && !apiObjectField.processtemplate())) { value = getValue(Object.class, field.getGenericType(), fieldName, jsondocObjects); } else { value = getValue(field.getType(), field.getGenericType(), fieldName, jsondocObjects); @@ -61,7 +63,6 @@ public class JSONDocTemplateBuilder { jsonDocTemplate.put(fieldName, value); } - } catch (Exception e) { log.error("Error in JSONDocTemplate creation for class [" + clazz.getCanonicalName() + "]", e); } @@ -70,30 +71,24 @@ public class JSONDocTemplateBuilder { return jsonDocTemplate; } - private static Object getValue(Class fieldClass, Type fieldGenericType, String fieldName, Set> jsondocObjects) { + private static Object getValue(Class fieldClass, Type fieldGenericType, String fieldName, + Set> jsondocObjects) { if (fieldClass.isPrimitive()) { return getValue(wrap(fieldClass), null, fieldName, jsondocObjects); - } else if (Map.class.isAssignableFrom(fieldClass)) { return new HashMap(); - } else if (Number.class.isAssignableFrom(fieldClass)) { return new Integer(0); - } else if (String.class.isAssignableFrom(fieldClass) || fieldClass.isEnum()) { return new String(""); - } else if (Boolean.class.isAssignableFrom(fieldClass)) { return new Boolean("true"); - } else if (fieldClass.isArray() || Collection.class.isAssignableFrom(fieldClass)) { return new ArrayList(); - } else { return build(fieldClass, jsondocObjects); } - } private static Set getAllDeclaredFields(Class clazz) { @@ -133,5 +128,4 @@ public class JSONDocTemplateBuilder { private static Class wrap(Class clazz) { return clazz.isPrimitive() ? (Class) primitives.get(clazz) : clazz; } - } diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTypeBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTypeBuilder.java new file mode 100644 index 00000000..4584e7cf --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTypeBuilder.java @@ -0,0 +1,120 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Collection; +import java.util.Map; + +import org.jsondoc.core.annotation.ApiObject; +import org.jsondoc.core.util.JSONDocDefaultType; +import org.jsondoc.core.util.JSONDocType; + +public class JSONDocTypeBuilder { + + private static final String WILDCARD = "wildcard"; + + private static final String UNDEFINED = "undefined"; + + private static final String ARRAY = "array"; + + public static JSONDocType build(JSONDocType jsondocType, Class clazz, Type type) { + if (clazz.isAssignableFrom(JSONDocDefaultType.class)) { + jsondocType.addItemToType(UNDEFINED); + return jsondocType; + } + + if (Map.class.isAssignableFrom(clazz)) { + jsondocType.addItemToType(getCustomClassName(clazz)); + + if (type instanceof ParameterizedType) { + Type mapKeyType = ((ParameterizedType) type).getActualTypeArguments()[0]; + Type mapValueType = ((ParameterizedType) type).getActualTypeArguments()[1]; + + jsondocType.setMapKey(new JSONDocType()); + jsondocType.setMapValue(new JSONDocType()); + + if (mapKeyType instanceof Class) { + jsondocType.setMapKey(new JSONDocType(((Class) mapKeyType).getSimpleName())); + } else if (mapKeyType instanceof WildcardType) { + jsondocType.setMapKey(new JSONDocType(WILDCARD)); + } else if (mapKeyType instanceof TypeVariable) { + jsondocType.setMapKey(new JSONDocType(((TypeVariable) mapKeyType).getName())); + } else { + jsondocType.setMapKey( + build(jsondocType.getMapKey(), (Class) ((ParameterizedType) mapKeyType).getRawType(), + mapKeyType)); + } + + if (mapValueType instanceof Class) { + jsondocType.setMapValue(new JSONDocType(((Class) mapValueType).getSimpleName())); + } else if (mapValueType instanceof WildcardType) { + jsondocType.setMapValue(new JSONDocType(WILDCARD)); + } else if (mapValueType instanceof TypeVariable) { + jsondocType.setMapValue(new JSONDocType(((TypeVariable) mapValueType).getName())); + } else { + jsondocType.setMapValue( + build(jsondocType.getMapValue(), (Class) ((ParameterizedType) mapValueType).getRawType(), + mapValueType)); + } + } + } else if (Collection.class.isAssignableFrom(clazz)) { + if (type instanceof ParameterizedType) { + Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0]; + jsondocType.addItemToType(getCustomClassName(clazz)); + + if (parametrizedType instanceof Class) { + jsondocType.addItemToType(getCustomClassName((Class) parametrizedType)); + } else if (parametrizedType instanceof WildcardType) { + jsondocType.addItemToType(WILDCARD); + } else if (parametrizedType instanceof TypeVariable) { + jsondocType.addItemToType(((TypeVariable) parametrizedType).getName()); + } else { + return build(jsondocType, (Class) ((ParameterizedType) parametrizedType).getRawType(), + parametrizedType); + } + } else if (type instanceof GenericArrayType) { + return build(jsondocType, clazz, ((GenericArrayType) type).getGenericComponentType()); + } else { + jsondocType.addItemToType(getCustomClassName(clazz)); + } + } else if (clazz.isArray()) { + jsondocType.addItemToType(ARRAY); + Class componentType = clazz.getComponentType(); + return build(jsondocType, componentType, type); + } else { + jsondocType.addItemToType(getCustomClassName(clazz)); + if (type instanceof ParameterizedType) { + Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0]; + + if (parametrizedType instanceof Class) { + jsondocType.addItemToType(getCustomClassName((Class) parametrizedType)); + } else if (parametrizedType instanceof WildcardType) { + jsondocType.addItemToType(WILDCARD); + } else if (parametrizedType instanceof TypeVariable) { + jsondocType.addItemToType(((TypeVariable) parametrizedType).getName()); + } else { + return build(jsondocType, (Class) ((ParameterizedType) parametrizedType).getRawType(), + parametrizedType); + } + } + } + + return jsondocType; + } + + private static String getCustomClassName(Class clazz) { + if (clazz.isAnnotationPresent(ApiObject.class)) { + ApiObject annotation = clazz.getAnnotation(ApiObject.class); + if (annotation.name().isEmpty()) { + return clazz.getSimpleName().toLowerCase(); + } else { + return annotation.name(); + } + } else { + return clazz.getSimpleName(); + } + } +} diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringObjectBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringObjectBuilder.java new file mode 100644 index 00000000..46f38e8b --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringObjectBuilder.java @@ -0,0 +1,52 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Set; +import java.util.TreeSet; + +import org.jsondoc.core.pojo.ApiObjectDoc; +import org.jsondoc.core.pojo.ApiObjectFieldDoc; +import org.jsondoc.core.scanner.DefaultJSONDocScanner; +import org.jsondoc.core.util.JSONDocHibernateValidatorProcessor; +import org.jsondoc.core.util.JSONDocType; + +public class SpringObjectBuilder { + + public static ApiObjectDoc buildObject(Class clazz) { + ApiObjectDoc apiObjectDoc = new ApiObjectDoc(); + apiObjectDoc.setName(clazz.getSimpleName()); + + Set fieldDocs = new TreeSet(); + + for (Field field : clazz.getDeclaredFields()) { + ApiObjectFieldDoc fieldDoc = new ApiObjectFieldDoc(); + fieldDoc.setName(field.getName()); + fieldDoc.setOrder(Integer.MAX_VALUE); + fieldDoc.setRequired(DefaultJSONDocScanner.UNDEFINED.toUpperCase()); + fieldDoc.setJsondocType( + JSONDocTypeBuilder.build(new JSONDocType(), field.getType(), field.getGenericType())); + + JSONDocHibernateValidatorProcessor.processHibernateValidatorAnnotations(field, fieldDoc); + + fieldDocs.add(fieldDoc); + } + + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + ApiObjectDoc parentObjectDoc = buildObject(superclass); + fieldDocs.addAll(parentObjectDoc.getFields()); + } + + if (clazz.isEnum()) { + apiObjectDoc.setAllowedvalues(DefaultJSONDocScanner.enumConstantsToStringArray(clazz.getEnumConstants())); + } + + apiObjectDoc.setFields(fieldDocs); + if (Modifier.isAbstract(clazz.getModifiers())) { + apiObjectDoc.setShow(false); + } + + return apiObjectDoc; + } +} diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringResponseBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringResponseBuilder.java new file mode 100644 index 00000000..4d8c0ed9 --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringResponseBuilder.java @@ -0,0 +1,31 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.reflect.Method; + +import org.jsondoc.core.pojo.ApiResponseObjectDoc; +import org.jsondoc.core.util.JSONDocType; +import org.springframework.http.ResponseEntity; + +public class SpringResponseBuilder { + + /** + * Builds the ApiResponseObjectDoc from the method's return type and checks if the first type corresponds to a + * ResponseEntity class. In that case removes the "responseentity" string from the final list because it's not + * important to the documentation user. + * + * @param method + * the method to create the response object for + * + * @return the created {@link ApiResponseObjectDoc} + */ + public static ApiResponseObjectDoc buildResponse(Method method) { + ApiResponseObjectDoc apiResponseObjectDoc = new ApiResponseObjectDoc( + JSONDocTypeBuilder.build(new JSONDocType(), method.getReturnType(), method.getGenericReturnType())); + + if (method.getReturnType().isAssignableFrom(ResponseEntity.class)) { + apiResponseObjectDoc.getJsondocType().getType().remove(0); + } + + return apiResponseObjectDoc; + } +} diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java b/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java index 0f5f5557..d7523577 100644 --- a/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java @@ -1,21 +1,18 @@ package org.luxons.sevenwonders.doc.scanner; -import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; import java.net.URL; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import com.google.common.collect.Sets; import org.jsondoc.core.annotation.Api; import org.jsondoc.core.annotation.ApiMethod; +import org.jsondoc.core.annotation.ApiObject; import org.jsondoc.core.pojo.ApiMethodDoc; +import org.jsondoc.core.pojo.ApiObjectDoc; import org.jsondoc.core.pojo.JSONDoc; import org.jsondoc.core.pojo.JSONDoc.MethodDisplay; import org.jsondoc.core.pojo.JSONDocTemplate; @@ -27,12 +24,14 @@ import org.jsondoc.springmvc.scanner.builder.SpringHeaderBuilder; import org.jsondoc.springmvc.scanner.builder.SpringPathVariableBuilder; import org.jsondoc.springmvc.scanner.builder.SpringProducesBuilder; import org.jsondoc.springmvc.scanner.builder.SpringQueryParamBuilder; -import org.jsondoc.springmvc.scanner.builder.SpringResponseBuilder; import org.jsondoc.springmvc.scanner.builder.SpringResponseStatusBuilder; import org.jsondoc.springmvc.scanner.builder.SpringVerbBuilder; +import org.luxons.sevenwonders.doc.builders.JSONDocApiObjectDocBuilder; import org.luxons.sevenwonders.doc.builders.JSONDocTemplateBuilder; +import org.luxons.sevenwonders.doc.builders.SpringObjectBuilder; import org.luxons.sevenwonders.doc.builders.SpringPathBuilder; import org.luxons.sevenwonders.doc.builders.SpringRequestBodyBuilder; +import org.luxons.sevenwonders.doc.builders.SpringResponseBuilder; import org.reflections.Reflections; import org.reflections.scanners.MethodAnnotationsScanner; import org.reflections.util.ClasspathHelper; @@ -46,11 +45,9 @@ import org.springframework.web.bind.annotation.RequestMapping; public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { - /** - * Returns the main ApiDoc, containing ApiMethodDoc and ApiObjectDoc objects - * @return An ApiDoc object - */ - public JSONDoc getJSONDoc(String version, String basePath, List packages, boolean playgroundEnabled, MethodDisplay displayMethodAs) { + @Override + public JSONDoc getJSONDoc(String version, String basePath, List packages, boolean playgroundEnabled, + MethodDisplay displayMethodAs) { Set urls = new HashSet(); FilterBuilder filter = new FilterBuilder(); @@ -61,7 +58,9 @@ public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { filter.includePackage(pkg); } - reflections = new Reflections(new ConfigurationBuilder().filterInputsBy(filter).setUrls(urls).addScanners(new MethodAnnotationsScanner())); + reflections = new Reflections(new ConfigurationBuilder().filterInputsBy(filter) + .setUrls(urls) + .addScanners(new MethodAnnotationsScanner())); JSONDoc jsondocDoc = new JSONDoc(version, basePath); jsondocDoc.setPlaygroundEnabled(playgroundEnabled); @@ -138,84 +137,20 @@ public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { @Override public Set> jsondocObjects(List packages) { - Set> candidates = getRootApiObjects(); - Set> subCandidates = Sets.newHashSet(); - - // This is to get objects' fields that are not returned nor part of the body request of a method, but that - // are a field of an object returned or a body of a request of a method - for (Class clazz : candidates) { - appendSubCandidates(clazz, subCandidates); - } - candidates.addAll(subCandidates); - - return candidates.stream().filter(clazz -> inWhiteListedPackages(packages, clazz)).collect(Collectors.toSet()); - } - - private Set> getRootApiObjects() { - Set> candidates = Sets.newHashSet(); - Set methodsToDocument = getMethodsToDocument(); - for (Method method : methodsToDocument) { - addReturnType(candidates, method); - addBodyParam(candidates, method); - } - return candidates; - } - - private void addReturnType(Set> candidates, Method method) { - Class returnValueClass = method.getReturnType(); - if (returnValueClass.isPrimitive() || returnValueClass.equals(JSONDoc.class)) { - return; - } - buildJSONDocObjectsCandidates(candidates, returnValueClass, method.getGenericReturnType(), - reflections); - } - - private void addBodyParam(Set> candidates, Method method) { - int bodyParamIndex = SpringRequestBodyBuilder.getIndexOfBodyParam(method); - if (bodyParamIndex >= 0) { - Class bodyParamClass = method.getParameterTypes()[bodyParamIndex]; - Type bodyParamType = method.getGenericParameterTypes()[bodyParamIndex]; - buildJSONDocObjectsCandidates(candidates, bodyParamClass, bodyParamType, reflections); - } + return new ObjectsScanner(reflections).findJsondocObjects(packages); } - private Set getMethodsToDocument() { - Set methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(RequestMapping.class); - methodsAnnotatedWith.addAll(reflections.getMethodsAnnotatedWith(SubscribeMapping.class)); - methodsAnnotatedWith.addAll(reflections.getMethodsAnnotatedWith(MessageMapping.class)); - return methodsAnnotatedWith; - } - - private boolean inWhiteListedPackages(List packages, Class clazz) { - Package p = clazz.getPackage(); - return p != null && packages.stream().anyMatch(whiteListedPkg -> p.getName().startsWith(whiteListedPkg)); + @Override + public ApiObjectDoc initApiObjectDoc(Class clazz) { + return SpringObjectBuilder.buildObject(clazz); } - private void appendSubCandidates(Class clazz, Set> subCandidates) { - if (clazz.isPrimitive() || clazz.equals(Class.class)) { - return; - } - - for (Field field : clazz.getDeclaredFields()) { - if (!isValidForRecursion(field)) { - continue; - } - - Class fieldClass = field.getType(); - Set> fieldCandidates = new HashSet<>(); - buildJSONDocObjectsCandidates(fieldCandidates, fieldClass, field.getGenericType(), reflections); - - for (Class candidate : fieldCandidates) { - if (!subCandidates.contains(candidate)) { - subCandidates.add(candidate); - - appendSubCandidates(candidate, subCandidates); - } - } + @Override + public ApiObjectDoc mergeApiObjectDoc(Class clazz, ApiObjectDoc apiObjectDoc) { + if (clazz.isAnnotationPresent(ApiObject.class)) { + ApiObjectDoc jsondocApiObjectDoc = JSONDocApiObjectDocBuilder.build(clazz); + BeanUtils.copyProperties(jsondocApiObjectDoc, apiObjectDoc); } - } - - private static boolean isValidForRecursion(Field field) { - return !field.isSynthetic() && !field.getType().isPrimitive() && !Modifier.isTransient(field.getModifiers()); + return apiObjectDoc; } } diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/ObjectsScanner.java b/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/ObjectsScanner.java new file mode 100644 index 00000000..e20406ef --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/ObjectsScanner.java @@ -0,0 +1,209 @@ +package org.luxons.sevenwonders.doc.scanner; + +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Sets; +import org.jsondoc.core.pojo.JSONDoc; +import org.luxons.sevenwonders.doc.builders.SpringRequestBodyBuilder; +import org.reflections.Reflections; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.annotation.SubscribeMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +public class ObjectsScanner { + + private final Reflections reflections; + + public ObjectsScanner(Reflections reflections) { + this.reflections = reflections; + } + + public Set> findJsondocObjects(List packages) { + Set> candidates = getRootApiObjects(); + Set> subCandidates = Sets.newHashSet(); + + // This is to get objects' fields that are not returned nor part of the body request of a method, but that + // are a field of an object returned or a body of a request of a method + for (Class clazz : candidates) { + appendSubCandidates(clazz, subCandidates); + } + candidates.addAll(subCandidates); + + return candidates.stream().filter(clazz -> inWhiteListedPackages(packages, clazz)).collect(Collectors.toSet()); + } + + private Set> getRootApiObjects() { + Set> candidates = Sets.newHashSet(); + Set methodsToDocument = getMethodsToDocument(); + for (Method method : methodsToDocument) { + addReturnType(candidates, method); + addBodyParam(candidates, method); + } + return candidates; + } + + private void addReturnType(Set> candidates, Method method) { + Class returnValueClass = method.getReturnType(); + if (returnValueClass.isPrimitive() || returnValueClass.equals(JSONDoc.class)) { + return; + } + buildJSONDocObjectsCandidates(candidates, returnValueClass, method.getGenericReturnType(), reflections); + } + + private void addBodyParam(Set> candidates, Method method) { + int bodyParamIndex = SpringRequestBodyBuilder.getIndexOfBodyParam(method); + if (bodyParamIndex >= 0) { + Class bodyParamClass = method.getParameterTypes()[bodyParamIndex]; + Type bodyParamType = method.getGenericParameterTypes()[bodyParamIndex]; + buildJSONDocObjectsCandidates(candidates, bodyParamClass, bodyParamType, reflections); + } + } + + private Set getMethodsToDocument() { + Set methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(RequestMapping.class); + methodsAnnotatedWith.addAll(reflections.getMethodsAnnotatedWith(SubscribeMapping.class)); + methodsAnnotatedWith.addAll(reflections.getMethodsAnnotatedWith(MessageMapping.class)); + return methodsAnnotatedWith; + } + + private boolean inWhiteListedPackages(List packages, Class clazz) { + Package p = clazz.getPackage(); + return p != null && packages.stream().anyMatch(whiteListedPkg -> p.getName().startsWith(whiteListedPkg)); + } + + private void appendSubCandidates(Class clazz, Set> subCandidates) { + if (clazz.isPrimitive() || clazz.equals(Class.class)) { + return; + } + + for (Field field : clazz.getDeclaredFields()) { + if (!isValidForRecursion(field)) { + continue; + } + + Class fieldClass = field.getType(); + Set> fieldCandidates = new HashSet<>(); + buildJSONDocObjectsCandidates(fieldCandidates, fieldClass, field.getGenericType(), reflections); + + for (Class candidate : fieldCandidates) { + if (!subCandidates.contains(candidate)) { + subCandidates.add(candidate); + + appendSubCandidates(candidate, subCandidates); + } + } + } + } + + private static boolean isValidForRecursion(Field field) { + // return !field.isSynthetic() && !field.getType().isPrimitive() && !Modifier.isTransient(field + // .getModifiers()); + return true; + } + + public static Set> buildJSONDocObjectsCandidates(Set> candidates, Class clazz, Type type, + Reflections reflections) { + + if (Map.class.isAssignableFrom(clazz)) { + + if (type instanceof ParameterizedType) { + Type mapKeyType = ((ParameterizedType) type).getActualTypeArguments()[0]; + Type mapValueType = ((ParameterizedType) type).getActualTypeArguments()[1]; + + if (mapKeyType instanceof Class) { + candidates.add((Class) mapKeyType); + } else if (mapKeyType instanceof WildcardType) { + candidates.add(Void.class); + } else { + if (mapKeyType instanceof ParameterizedType) { + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) mapKeyType).getRawType(), mapKeyType, reflections)); + } + } + + if (mapValueType instanceof Class) { + candidates.add((Class) mapValueType); + } else if (mapValueType instanceof WildcardType) { + candidates.add(Void.class); + } else { + if (mapValueType instanceof ParameterizedType) { + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) mapValueType).getRawType(), mapValueType, reflections)); + } + } + } + } else if (Collection.class.isAssignableFrom(clazz)) { + if (type instanceof ParameterizedType) { + Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0]; + candidates.add(clazz); + + if (parametrizedType instanceof Class) { + candidates.add((Class) parametrizedType); + } else if (parametrizedType instanceof WildcardType) { + candidates.add(Void.class); + } else { + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) parametrizedType).getRawType(), parametrizedType, + reflections)); + } + } else if (type instanceof GenericArrayType) { + candidates.addAll(buildJSONDocObjectsCandidates(candidates, clazz, + ((GenericArrayType) type).getGenericComponentType(), reflections)); + } else { + candidates.add(clazz); + } + } else if (clazz.isArray()) { + Class componentType = clazz.getComponentType(); + candidates.addAll(buildJSONDocObjectsCandidates(candidates, componentType, type, reflections)); + } else { + if (type instanceof ParameterizedType) { + Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0]; + + if (parametrizedType instanceof Class) { + Class candidate = (Class) parametrizedType; + if (candidate.isInterface()) { + for (Class implementation : reflections.getSubTypesOf(candidate)) { + buildJSONDocObjectsCandidates(candidates, implementation, parametrizedType, reflections); + } + } else { + candidates.add(candidate); + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) type).getRawType(), parametrizedType, reflections)); + } + } else if (parametrizedType instanceof WildcardType) { + candidates.add(Void.class); + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) type).getRawType(), parametrizedType, reflections)); + } else if (parametrizedType instanceof TypeVariable) { + candidates.add(Void.class); + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) type).getRawType(), parametrizedType, reflections)); + } else { + candidates.addAll(buildJSONDocObjectsCandidates(candidates, + (Class) ((ParameterizedType) parametrizedType).getRawType(), parametrizedType, + reflections)); + } + } else if (clazz.isInterface()) { + for (Class implementation : reflections.getSubTypesOf(clazz)) { + candidates.addAll(buildJSONDocObjectsCandidates(candidates, implementation, type, reflections)); + } + } else { + candidates.add(clazz); + } + } + + return candidates; + } +} -- cgit