JCodeModel.parseType(String) silently ignores type params in specific scenarios (#1517)

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
diff --git a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCodeModel.java b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCodeModel.java
index cf61b7d..882258d 100644
--- a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCodeModel.java
+++ b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCodeModel.java
@@ -18,8 +18,10 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import com.sun.codemodel.writer.FileCodeWriter;
 import com.sun.codemodel.writer.ProgressCodeWriter;
@@ -459,7 +461,95 @@
         }
 
         // existing class
-        return new TypeNameParser(name).parseTypeName();
+//        return new TypeNameParser(name).parseTypeName();
+        return new TreeParser().parseTypeName(name);
+    }
+
+    private class TreeParser {
+
+        private Node buildTree(String str) {
+            StringBuilder content = new StringBuilder();
+            Node root = new Node(null);
+            root.value = str;
+            Node current = root;
+            for (int i = 0; i < str.length(); i++) {
+                char c = str.charAt(i);
+                if (c == '<') {
+                    Node child = new Node(current);
+                    current.value = content.toString();
+                    current.childs.add(child);
+                    current = child;
+                    content = new StringBuilder();
+                } else if (c == '>') {
+                    if (current.value == null) {
+                        current.value = content.toString();
+                    }
+                    current = current.parent;
+                    content = new StringBuilder();
+                } else if (c == ',') {
+                    if (current.value == null) {
+                        current.value = content.toString();
+                    }
+                    Node brother = new Node(current.parent);
+                    brother.parent.childs.add(brother);
+                    current = brother;
+                    content = new StringBuilder();
+                } else {
+                    content.append(c);
+                }
+            }
+            return root;
+        }
+
+        private void postOrderCreateJClass(Node node) throws ClassNotFoundException {
+            if (node != null) {
+                for (Node child : node.childs) {
+                    postOrderCreateJClass(child);
+                }
+                node.jClass =  new TypeNameParser(node.value).parseTypeName();
+                if (!node.childs.isEmpty()) {
+                    List<JClass> args = node.childs.stream().map(n -> n.jClass).collect(Collectors.toList());
+                    JClass[] argsA = args.toArray(new JClass[args.size()]);
+                    JClass clazz = node.jClass.narrow(argsA);
+                    node.jClass = clazz;
+                }
+            }
+        }
+
+        private JClass parseTypeName(String str) throws ClassNotFoundException {
+            Node root = buildTree(str);
+            postOrderCreateJClass(root);
+            return root.jClass;
+        }
+    }
+
+    private static class Node {
+        private String value;
+        private JClass jClass;
+        private final Node parent;
+        private final List<Node> childs = new LinkedList<>();
+
+        public Node(Node parent) {
+            this.parent = parent;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder(value.toString());
+            boolean hasChilds = !childs.isEmpty();
+            if (hasChilds) {
+                builder.append("<");
+            }
+            for (Node child : childs) {
+                builder.append(child.toString()).append(",");
+            }
+            if (hasChilds) {
+                // Remove last comma
+                builder.deleteCharAt(builder.length() - 1);
+                builder.append(">");
+            }
+            return builder.toString();
+        }
     }
 
     private final class TypeNameParser {
diff --git a/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/Issue1505Test.java b/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/Issue1505Test.java
new file mode 100644
index 0000000..563cfed
--- /dev/null
+++ b/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/Issue1505Test.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.codemodel;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class Issue1505Test {
+
+    private void checks(String test) {
+        checks(test, test);
+    }
+
+    private void checks(String expected, String test) {
+        JCodeModel model = new JCodeModel();
+        try {
+            JType type = model.parseType(test);
+            assertEquals(expected, type.fullName());
+        } catch (ClassNotFoundException e) {
+            fail(e.getMessage());
+        } 
+    }
+
+    @Test
+    public void test1() {
+        checks("Map<K,Pair<X,Y>>");
+    }
+
+    @Test
+    public void test2() {
+        checks("Map<Pair<X,Y>,V>");
+    }
+
+    @Test
+    public void test3() {
+        checks("M<K,V<X>>");
+    }
+
+    @Test
+    public void test4() {
+        checks("M<K<X>,V>");
+    }
+
+    @Test
+    public void test5() {
+        checks("A<B,C>");
+    }
+
+    @Test
+    public void test6() {
+        checks("M<K>");
+    }
+
+    @Test
+    public void test7() {
+        checks("M<K<Q>>");
+    }
+
+    @Test
+    public void test8() {
+        checks("M<K,V>");
+    }
+
+    @Test
+    public void test9() {
+        checks("M<K,V<Q>>");
+    }
+
+    @Test
+    @Ignore("Not supported")
+    public void test10() {
+        checks("java.util.Map<K extends ?,V extends ?>");
+    }
+
+    @Test
+    public void test11() {
+        checks("Map<Key,Value<Que>>");
+    }
+
+    @Test
+    public void test12() {
+        checks("M<K<T>,V>");
+    }
+
+    @Test
+    public void test13() {
+        checks("M<T,Q,R>");
+    }
+
+    @Test
+    public void test14() {
+        checks("M<A,B<C,D<E>,F<G>>>");
+    }
+
+    @Test
+    public void test15() {
+        checks("M<A,B[]<C[],D[]<E>,F<G[]>>>");
+    }
+
+    @Test
+    public void test16() {
+        checks("M<? extends A,? extends B>");
+    }
+
+    @Test
+    @Ignore("Not supported")
+    public void test17() {
+        checks("M<A extends Object,B extends Object>");
+    }
+
+    @Test
+    public void test18() {
+        checks("java.lang.Object");
+    }
+
+    @Test
+    public void test19() {
+        checks("java.util.ArrayList<String>");
+    }
+}