1212package com .microsoft .jdtls .ext .core .parser ;
1313
1414import java .util .ArrayList ;
15+ import java .util .HashSet ;
1516import java .util .List ;
1617import java .util .Set ;
1718import java .util .regex .Matcher ;
3132 */
3233public class ContextResolver {
3334
35+ // Pre-compiled regex patterns for performance
36+ private static final Pattern MARKDOWN_CODE_PATTERN = Pattern .compile ("(?s)```(?:java)?\\ n?(.*?)```" );
37+ private static final Pattern HTML_PRE_PATTERN = Pattern .compile ("(?is)<pre[^>]*>(.*?)</pre>" );
38+ private static final Pattern HTML_CODE_PATTERN = Pattern .compile ("(?is)<code[^>]*>(.*?)</code>" );
39+
40+ // Constants for limiting displayed members
41+ private static final int MAX_METHODS_TO_DISPLAY = 10 ;
42+ private static final int MAX_FIELDS_TO_DISPLAY = 10 ;
43+ private static final int MAX_STATIC_METHODS_TO_DISPLAY = 10 ;
44+ private static final int MAX_STATIC_FIELDS_TO_DISPLAY = 10 ;
45+
3446 /**
3547 * ImportClassInfo - Conforms to Copilot CodeSnippet format
3648 * Used to provide Java class context information and JavaDoc to Copilot
@@ -170,7 +182,7 @@ public static void resolveStaticMembersFromClass(IJavaProject javaProject, Strin
170182 for (IMethod method : methods ) {
171183 int flags = method .getFlags ();
172184 if (org .eclipse .jdt .core .Flags .isStatic (flags ) && org .eclipse .jdt .core .Flags .isPublic (flags )) {
173- if (staticMethodSigs .size () < 10 ) {
185+ if (staticMethodSigs .size () < MAX_STATIC_METHODS_TO_DISPLAY ) {
174186 staticMethodSigs .add (generateMethodSignature (method ));
175187 }
176188 }
@@ -182,7 +194,7 @@ public static void resolveStaticMembersFromClass(IJavaProject javaProject, Strin
182194 for (org .eclipse .jdt .core .IField field : fields ) {
183195 int flags = field .getFlags ();
184196 if (org .eclipse .jdt .core .Flags .isStatic (flags ) && org .eclipse .jdt .core .Flags .isPublic (flags )) {
185- if (staticFieldSigs .size () < 10 ) {
197+ if (staticFieldSigs .size () < MAX_STATIC_FIELDS_TO_DISPLAY ) {
186198 staticFieldSigs .add (generateFieldSignature (field ));
187199 }
188200 }
@@ -321,15 +333,12 @@ public static void extractTypeInfo(org.eclipse.jdt.core.IType type, List<ImportC
321333 return ;
322334 }
323335
324- // Generate human-readable class description
325- String description = generateClassDescription (type );
326-
327- // Extract relevant JavaDoc content (code snippets with fallback strategy)
336+ // Extract relevant JavaDoc content first (code snippets with fallback strategy)
328337 // This uses a hybrid approach: AST extraction -> HTML extraction -> Markdown extraction -> fallback
329338 String relevantJavadoc = extractRelevantJavaDocContent (type , monitor );
330- if ( isNotEmpty ( relevantJavadoc )) {
331- description = description + " \n " + relevantJavadoc ;
332- }
339+
340+ // Generate human-readable class description with JavaDoc inserted after signature
341+ String description = generateClassDescription ( type , relevantJavadoc );
333342
334343 // Create ImportClassInfo (conforms to Copilot CodeSnippet format)
335344 ImportClassInfo info = new ImportClassInfo (uri , description );
@@ -385,8 +394,10 @@ public static String getTypeUri(org.eclipse.jdt.core.IType type) {
385394
386395 /**
387396 * Generate complete class description (natural language format, similar to JavaDoc)
397+ * @param type the Java type to describe
398+ * @param javadoc optional JavaDoc content to insert after signature (can be null or empty)
388399 */
389- public static String generateClassDescription (org .eclipse .jdt .core .IType type ) {
400+ public static String generateClassDescription (org .eclipse .jdt .core .IType type , String javadoc ) {
390401 StringBuilder description = new StringBuilder ();
391402
392403 try {
@@ -449,7 +460,12 @@ public static String generateClassDescription(org.eclipse.jdt.core.IType type) {
449460
450461 description .append ("Signature: " ).append (signature ).append ("\n \n " );
451462
452- // === 2. Constructors ===
463+ // === 2. JavaDoc (inserted after signature) ===
464+ if (isNotEmpty (javadoc )) {
465+ description .append ("JavaDoc:\n " ).append (javadoc ).append ("\n \n " );
466+ }
467+
468+ // === 3. Constructors ===
453469 IMethod [] methods = type .getMethods ();
454470 List <String > constructorSigs = new ArrayList <>();
455471
@@ -467,13 +483,13 @@ public static String generateClassDescription(org.eclipse.jdt.core.IType type) {
467483 description .append ("\n " );
468484 }
469485
470- // === 3 . Public methods (limited to first 10) ===
486+ // === 4 . Public methods (limited to first 10) ===
471487 List <String > methodSigs = new ArrayList <>();
472488 int methodCount = 0 ;
473489
474490 for (IMethod method : methods ) {
475491 if (!method .isConstructor () && org .eclipse .jdt .core .Flags .isPublic (method .getFlags ())) {
476- if (methodCount < 10 ) {
492+ if (methodCount < MAX_METHODS_TO_DISPLAY ) {
477493 methodSigs .add (generateMethodSignature (method ));
478494 methodCount ++;
479495 } else {
@@ -487,19 +503,19 @@ public static String generateClassDescription(org.eclipse.jdt.core.IType type) {
487503 for (String sig : methodSigs ) {
488504 description .append (" - " ).append (sig ).append ("\n " );
489505 }
490- if (methodCount == 10 && methods .length > methodCount ) {
506+ if (methodCount == MAX_METHODS_TO_DISPLAY && methods .length > methodCount ) {
491507 description .append (" - ... (more methods available)\n " );
492508 }
493509 description .append ("\n " );
494510 }
495511
496- // === 4 . Public fields (limited to first 10) ===
512+ // === 5 . Public fields (limited to first 10) ===
497513 org .eclipse .jdt .core .IField [] fields = type .getFields ();
498514 List <String > fieldSigs = new ArrayList <>();
499515 int fieldCount = 0 ;
500516
501517 for (org .eclipse .jdt .core .IField field : fields ) {
502- if (org .eclipse .jdt .core .Flags .isPublic (field .getFlags ()) && fieldCount < 10 ) {
518+ if (org .eclipse .jdt .core .Flags .isPublic (field .getFlags ()) && fieldCount < MAX_FIELDS_TO_DISPLAY ) {
503519 fieldSigs .add (generateFieldSignature (field ));
504520 fieldCount ++;
505521 }
@@ -552,13 +568,13 @@ private static String extractRelevantJavaDocContent(org.eclipse.jdt.core.IType t
552568 }
553569
554570 StringBuilder allCodeSnippets = new StringBuilder ();
571+ Set <String > seenCodeSnippets = new HashSet <>();
555572
556573 // 1. Extract markdown code blocks (```...```)
557- Pattern markdownPattern = Pattern .compile ("(?s)```(?:java)?\\ n?(.*?)```" );
558- Matcher markdownMatcher = markdownPattern .matcher (rawJavadoc );
574+ Matcher markdownMatcher = MARKDOWN_CODE_PATTERN .matcher (rawJavadoc );
559575 while (markdownMatcher .find ()) {
560576 String code = markdownMatcher .group (1 ).trim ();
561- if (isNotEmpty (code )) {
577+ if (isNotEmpty (code ) && seenCodeSnippets . add ( code ) ) {
562578 allCodeSnippets .append ("```java\n " ).append (code ).append ("\n ```\n \n " );
563579 }
564580 }
@@ -569,22 +585,20 @@ private static String extractRelevantJavaDocContent(org.eclipse.jdt.core.IType t
569585 cleanedForHtml = convertHtmlEntities (cleanedForHtml );
570586
571587 // Priority 1: <pre> blocks (often contain well-formatted code)
572- Pattern prePattern = Pattern .compile ("(?is)<pre[^>]*>(.*?)</pre>" );
573- Matcher preMatcher = prePattern .matcher (cleanedForHtml );
588+ Matcher preMatcher = HTML_PRE_PATTERN .matcher (cleanedForHtml );
574589 while (preMatcher .find ()) {
575590 String code = preMatcher .group (1 ).replaceAll ("(?i)<code[^>]*>" , "" ).replaceAll ("(?i)</code>" , "" ).trim ();
576- if (isNotEmpty (code )) {
591+ if (isNotEmpty (code ) && seenCodeSnippets . add ( code ) ) {
577592 allCodeSnippets .append ("```java\n " ).append (code ).append ("\n ```\n \n " );
578593 }
579594 }
580595
581596 // Priority 2: <code> blocks (for inline snippets)
582- Pattern codePattern = Pattern .compile ("(?is)<code[^>]*>(.*?)</code>" );
583- Matcher codeMatcher = codePattern .matcher (cleanedForHtml );
597+ Matcher codeMatcher = HTML_CODE_PATTERN .matcher (cleanedForHtml );
584598 while (codeMatcher .find ()) {
585599 String code = codeMatcher .group (1 ).trim ();
586- // Avoid adding duplicates that might be inside <pre><code>
587- if (isNotEmpty (code ) && allCodeSnippets . indexOf (code ) == - 1 ) {
600+ // Use HashSet for O(1) duplicate checking
601+ if (isNotEmpty (code ) && seenCodeSnippets . add (code )) {
588602 allCodeSnippets .append ("```java\n " ).append (code ).append ("\n ```\n \n " );
589603 }
590604 }
@@ -804,12 +818,18 @@ public static String convertTypeSignature(String jdtSignature) {
804818 if (jdtSignature .startsWith ("Q" ) && jdtSignature .endsWith (";" )) {
805819 baseType = jdtSignature .substring (1 , jdtSignature .length () - 1 );
806820 baseType = baseType .replace ('/' , '.' );
821+
822+ // Handle generic type parameters (e.g., "QResult<QUser;>;")
823+ baseType = processGenericTypes (baseType );
807824 baseType = simplifyTypeName (baseType );
808825 }
809826 // Handle fully qualified types (starts with L)
810827 else if (jdtSignature .startsWith ("L" ) && jdtSignature .endsWith (";" )) {
811828 baseType = jdtSignature .substring (1 , jdtSignature .length () - 1 );
812829 baseType = baseType .replace ('/' , '.' );
830+
831+ // Handle generic type parameters
832+ baseType = processGenericTypes (baseType );
813833 baseType = simplifyTypeName (baseType );
814834 }
815835 // Handle primitive types
@@ -835,16 +855,78 @@ public static String convertTypeSignature(String jdtSignature) {
835855
836856 return baseType ;
837857 }
858+
859+ /**
860+ * Process generic type parameters in a type name
861+ * Example: "Result<QUser;>" -> "Result<User>"
862+ */
863+ private static String processGenericTypes (String typeName ) {
864+ if (typeName == null || !typeName .contains ("<" )) {
865+ return typeName ;
866+ }
867+
868+ StringBuilder result = new StringBuilder ();
869+ int i = 0 ;
870+
871+ while (i < typeName .length ()) {
872+ char c = typeName .charAt (i );
873+
874+ if (c == '<' || c == ',' || c == ' ' ) {
875+ // Keep angle brackets, commas, and spaces
876+ result .append (c );
877+ i ++;
878+
879+ // Skip whitespace after comma or opening bracket
880+ while (i < typeName .length () && typeName .charAt (i ) == ' ' ) {
881+ result .append (' ' );
882+ i ++;
883+ }
884+
885+ // Check if next is a type parameter (Q or L prefix)
886+ if (i < typeName .length ()) {
887+ char next = typeName .charAt (i );
888+
889+ if (next == 'Q' || next == 'L' ) {
890+ // Find the end of this type parameter (marked by ;)
891+ int endIndex = typeName .indexOf (';' , i );
892+ if (endIndex != -1 ) {
893+ // Extract the type parameter and convert it
894+ String typeParam = typeName .substring (i + 1 , endIndex );
895+
896+ // Recursively process nested generics
897+ typeParam = processGenericTypes (typeParam );
898+ typeParam = simplifyTypeName (typeParam );
899+
900+ result .append (typeParam );
901+ i = endIndex + 1 ; // Skip past the semicolon
902+ } else {
903+ result .append (next );
904+ i ++;
905+ }
906+ } else {
907+ // Not a type parameter, just append
908+ result .append (next );
909+ i ++;
910+ }
911+ }
912+ } else {
913+ result .append (c );
914+ i ++;
915+ }
916+ }
917+
918+ return result .toString ();
919+ }
838920
839921 /**
840922 * Simplify fully qualified type name to just the simple name
841923 */
842924 private static String simplifyTypeName (String qualifiedName ) {
843- if (qualifiedName == null || ! qualifiedName . contains ( "." ) ) {
925+ if (qualifiedName == null ) {
844926 return qualifiedName ;
845927 }
846- String [] parts = qualifiedName .split ( " \\ ." );
847- return parts [ parts . length - 1 ] ;
928+ int lastDot = qualifiedName .lastIndexOf ( '.' );
929+ return lastDot == - 1 ? qualifiedName : qualifiedName . substring ( lastDot + 1 ) ;
848930 }
849931
850932 /**
0 commit comments