You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2020/09/16 07:17:14 UTC

[camel] 02/04: CAMEL-15478: Java source parser should resolve return type that may be generic and parameterized.

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch api
in repository https://gitbox.apache.org/repos/asf/camel.git

commit e4f5744ec65a9a5c9d36faa82f0292899148d278
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Sep 16 09:00:19 2020 +0200

    CAMEL-15478: Java source parser should resolve return type that may be generic and parameterized.
---
 .../org/apache/camel/maven/JavaSourceParser.java   | 88 ++++++++++++++++++----
 1 file changed, 75 insertions(+), 13 deletions(-)

diff --git a/tooling/maven/camel-api-component-maven-plugin/src/main/java/org/apache/camel/maven/JavaSourceParser.java b/tooling/maven/camel-api-component-maven-plugin/src/main/java/org/apache/camel/maven/JavaSourceParser.java
index 60286b0..a05596e 100644
--- a/tooling/maven/camel-api-component-maven-plugin/src/main/java/org/apache/camel/maven/JavaSourceParser.java
+++ b/tooling/maven/camel-api-component-maven-plugin/src/main/java/org/apache/camel/maven/JavaSourceParser.java
@@ -37,6 +37,8 @@ import org.jboss.forge.roaster.model.source.MethodHolderSource;
 import org.jboss.forge.roaster.model.source.MethodSource;
 import org.jboss.forge.roaster.model.source.ParameterSource;
 import org.jboss.forge.roaster.model.source.TypeVariableSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.apache.camel.tooling.util.JavadocHelper.sanitizeDescription;
 
@@ -45,6 +47,8 @@ import static org.apache.camel.tooling.util.JavadocHelper.sanitizeDescription;
  */
 public class JavaSourceParser {
 
+    private static final Logger LOG = LoggerFactory.getLogger(JavaSourceParser.class);
+
     private List<String> methods = new ArrayList<>();
     private Map<String, String> methodText = new HashMap<>();
     private Map<String, Map<String, String>> parameters = new LinkedHashMap<>();
@@ -53,6 +57,65 @@ public class JavaSourceParser {
     private String apiDescription;
     private final Map<String, String> methodDescriptions = new HashMap<>();
 
+    private static String resolveParameterizedType(
+            AbstractGenericCapableJavaSource clazz, MethodSource ms, ParameterSource ps, Type type) {
+        String answer = resolveType(clazz, clazz, ms, type);
+
+        if (type.isParameterized()) {
+            // for parameterized types then it can get complex if they are variables (T, T extends Foo etc)
+            // or if there are no bounds for these types which we then can't resolve.
+            List<Type> types = type.getTypeArguments();
+            boolean bounds = false;
+            boolean found = false;
+            for (Type t : types) {
+                if (hasTypeVariableBounds(ms, clazz, t.getName())) {
+                    bounds = true;
+                    // okay now it gets complex as we have a type like T which is a type variable and we need to resolve that into
+                    // what base class that is
+                    String tn = resolveTypeVariable(ms, clazz, t.getName());
+                    if (tn != null) {
+                        answer = answer.replace(t.getName(), tn);
+                        found = true;
+                    }
+                }
+            }
+            if (!bounds && !found) {
+                // argh this is getting complex, it may be T or just java.lang.String but this **** generics and roaster
+                // does not make this easy, so let see if we can find out if all the types are a qualified type or only a variable
+                boolean fqn = types.stream().allMatch(t -> {
+                    // if its from java itself then its okay
+                    if (t.getQualifiedName().startsWith("java")) {
+                        return true;
+                    }
+                    // okay lets assume its a type variable if the name is upper case only
+                    boolean upperOnly = isUpperCaseOnly(t.getName());
+                    return !upperOnly && t.getQualifiedName().indexOf('.') != -1;
+                });
+                if (!fqn) {
+                    // remove generics we could not resolve that even if we have bounds information
+                    bounds = true;
+                    found = false;
+                }
+            }
+            if (bounds && !found) {
+                // remove generics we could not resolve that even if we have bounds information
+                answer = type.getQualifiedName();
+            }
+        } else if (ms.hasTypeVariable(answer) || clazz.hasTypeVariable(answer)) {
+            // okay now it gets complex as we have a type like T which is a type variable and we need to resolve that into
+            // what base class that is
+            answer = resolveTypeVariable(ms, clazz, answer);
+        }
+        if ((ps != null && ps.isVarArgs()) || type.isArray()) {
+            // the old way with javadoc did not use varargs in the signature, so lets transform this to an array style
+            answer = answer + "[]";
+        }
+
+        // remove java.lang. prefix as it should not be there
+        answer = answer.replaceAll("java.lang.", "");
+        return answer;
+    }
+
     @SuppressWarnings("unchecked")
     public synchronized void parse(InputStream in, String innerClass) throws Exception {
         AbstractGenericCapableJavaSource rootClazz = (AbstractGenericCapableJavaSource) Roaster.parse(in);
@@ -67,6 +130,8 @@ public class JavaSourceParser {
             }
         }
 
+        LOG.debug("Parsing class: {}", clazz.getQualifiedName());
+
         String rawClass = clazz.toUnformattedString();
         String doc = getClassJavadocRaw(clazz, rawClass);
         apiDescription = sanitizeJavaDocValue(doc, true);
@@ -81,6 +146,9 @@ public class JavaSourceParser {
 
         List<MethodSource> ml = ((MethodHolderSource) clazz).getMethods();
         for (MethodSource ms : ml) {
+            String methodName = ms.getName();
+            LOG.debug("Parsing method: {}", methodName);
+
             // should not be constructor and must not be private
             boolean isInterface = clazz instanceof JavaInterfaceSource;
             boolean accept = isInterface || (!ms.isConstructor() && ms.isPublic());
@@ -97,20 +165,11 @@ public class JavaSourceParser {
                 methodDescriptions.put(ms.getName(), doc);
             }
 
-            String result;
-            Type rt = ms.getReturnType();
-            boolean hasTypeVariables = ms.hasTypeVariable(rt.getName()) || clazz.hasTypeVariable(rt.getName());
-            if (hasTypeVariables) {
-                // okay this gets to complex then remove the generics
-                result = "Object";
-            } else {
-                result = resolveType(rootClazz, clazz, ms, rt);
-                if (result == null || result.isEmpty()) {
-                    result = "void";
-                }
+            String result = resolveParameterizedType(clazz, ms, null, ms.getReturnType());
+            if (result.isEmpty()) {
+                result = "void";
             }
-            // remove java.lang. prefix as it should not be there
-            result = result.replaceAll("java.lang.", "");
+            LOG.trace("Parsed return type as: {}", result);
 
             List<JavaDocTag> params = ms.getJavaDoc().getTags("@param");
 
@@ -123,6 +182,9 @@ public class JavaSourceParser {
                 ParameterSource ps = list.get(i);
                 String name = ps.getName();
                 String type = resolveType(rootClazz, clazz, ms, ps.getType());
+                LOG.trace("Parsing parameter #{} ({} {})", i, type, name);
+
+                // TODO: Call that other method
                 if (ps.getType().isParameterized()) {
                     // for parameterized types then it can get complex if they are variables (T, T extends Foo etc)
                     // or if there are no bounds for these types which we then can't resolve.