4747import java .util .regex .Pattern ;
4848import java .util .stream .Collectors ;
4949import java .util .stream .Stream ;
50+ import java .util .zip .ZipEntry ;
51+ import java .util .zip .ZipFile ;
52+ import java .util .zip .ZipOutputStream ;
5053
5154public class GroovyDocToolTest extends GroovyTestCase {
5255 private static final String MOCK_DIR = "mock/doc" ;
@@ -824,6 +827,7 @@ public void testInheritDocResolvesForInterfaceMethodsInheritedThroughSuperclasse
824827 }
825828
826829 public void testInheritDocResolvesFromExternalJdkAbstractClassInHtml () throws Exception {
830+ if (skipIfNoJdkSrcZip ()) return ;
827831 String base = "org/codehaus/groovy/tools/groovydoc/testfiles" ;
828832 htmlTool .add (List .of (base + "/JavaExtendsWriterInheritDoc.java" ));
829833
@@ -836,15 +840,16 @@ public void testInheritDocResolvesFromExternalJdkAbstractClassInHtml() throws Ex
836840 assertNotNull ("Expected JavaExtendsWriterInheritDoc.html in output" , doc );
837841 assertNotNull ("Expected close() section in:\n " + doc , closeSection );
838842 assertNotNull ("Expected flush() section in:\n " + doc , flushSection );
839- assertTrue ("Expected inherited close() text from java.io.Writer in:\n " + doc ,
840- normalizeWhitespace ( closeSection ). contains ( "Closes the stream" ));
841- assertTrue ("Expected inherited flush() text from java.io.Writer in:\n " + doc ,
842- normalizeWhitespace ( flushSection ). contains ( "Flushes the stream" ));
843+ assertMatches ("Expected inherited close() text from java.io.Writer in:\n " + doc ,
844+ "(?i)close[sd]? \\ b[^ \\ n]{0,40} \\ bstream" , normalizeWhitespace ( closeSection ));
845+ assertMatches ("Expected inherited flush() text from java.io.Writer in:\n " + doc ,
846+ "(?i)flush(es|ed|ing)? \\ b[^ \\ n]{0,40} \\ bstream" , normalizeWhitespace ( flushSection ));
843847 assertFalse ("External JDK inheritDoc should not remain literal in:\n " + doc ,
844848 doc .contains ("{@inheritDoc}" ));
845849 }
846850
847851 public void testInheritDocResolvesFromExternalObjectMethodInHtml () throws Exception {
852+ if (skipIfNoJdkSrcZip ()) return ;
848853 String base = "org/codehaus/groovy/tools/groovydoc/testfiles" ;
849854 htmlTool .add (List .of (base + "/JavaObjectCloneInheritDocChild.java" ));
850855
@@ -855,8 +860,8 @@ public void testInheritDocResolvesFromExternalObjectMethodInHtml() throws Except
855860 String cloneSection = findMethodSection (doc , "clone" , "" );
856861 assertNotNull ("Expected JavaObjectCloneInheritDocChild.html in output" , doc );
857862 assertNotNull ("Expected clone() section in:\n " + doc , cloneSection );
858- assertTrue ("Expected inherited clone() text from java.lang.Object in:\n " + doc ,
859- normalizeWhitespace ( cloneSection ). contains ( "Creates and returns a copy of this object" ));
863+ assertMatches ("Expected inherited clone() text from java.lang.Object in:\n " + doc ,
864+ "(?i) copy\\ s+of \\ s+ this\\ s+ object", normalizeWhitespace ( cloneSection ));
860865 assertFalse ("External Object inheritDoc should not remain literal in:\n " + doc ,
861866 doc .contains ("{@inheritDoc}" ));
862867 }
@@ -903,6 +908,7 @@ public void testExternalJavadocSupportClearsCachesWhenSessionCloses() throws Exc
903908 }
904909
905910 public void testInheritDocResolvesFromExternalMapAndObjectMethodsInHtml () throws Exception {
911+ if (skipIfNoJdkSrcZip ()) return ;
906912 String base = "org/codehaus/groovy/tools/groovydoc/testfiles" ;
907913 htmlTool .add (List .of (base + "/JavaImplementsMapInheritDoc.java" ));
908914
@@ -919,16 +925,16 @@ public void testInheritDocResolvesFromExternalMapAndObjectMethodsInHtml() throws
919925 assertNotNull ("Expected containsValue(Object) section in:\n " + doc , containsValueSection );
920926 assertNotNull ("Expected equals(Object) section in:\n " + doc , equalsSection );
921927 assertNotNull ("Expected hashCode() section in:\n " + doc , hashCodeSection );
922- assertTrue ("Expected inherited clear() text from java.util.Map in:\n " + doc ,
923- normalizeWhitespace ( clearSection ). contains ( "Removes all of the mappings from this map" ));
924- assertTrue ("Expected inherited containsValue(Object) text from java.util.Map in:\n " + doc ,
925- containsValueSection . contains ( "Returns <CODE>true</CODE> if this map maps one or more keys to the" ));
926- assertTrue ("Expected inherited equals(Object) text from java.lang.Object in:\n " + doc ,
927- equalsSection . contains ( "Indicates whether some other object is \" equal to \" this one" ));
928- assertTrue ("Expected normalized inherited hashCode() text from java.lang.Object in:\n " + doc ,
929- normalizeWhitespace ( hashCodeSection ). contains ( "a hash code value for this object" ));
930- assertFalse ("Inherited external docs should not retain raw Javadoc comment markers in:\n " + doc ,
931- normalizeWhitespace ( doc ). contains ( "* Removes all of the mappings" ));
928+ assertMatches ("Expected inherited clear() text from java.util.Map in:\n " + doc ,
929+ "(?i)remov(es|ing)? \\ b[^ \\ n]{0,30} \\ bmapping" , normalizeWhitespace ( clearSection ));
930+ assertMatches ("Expected inherited containsValue(Object) text from java.util.Map in:\n " + doc ,
931+ "(?i) map\\ s+ maps? \\ s+ one\\ s+or \\ s+ more\\ s+key" , normalizeWhitespace ( containsValueSection ));
932+ assertMatches ("Expected inherited equals(Object) text from java.lang.Object in:\n " + doc ,
933+ "(?i) \\ bother \\ s+ object\\ b[^ \\ n]{0,40} \\ bequal" , normalizeWhitespace ( equalsSection ));
934+ assertMatches ("Expected normalized inherited hashCode() text from java.lang.Object in:\n " + doc ,
935+ "(?i) hash\\ s* code\\ b[^ \\ n]{0,30} \\ bobject" , normalizeWhitespace ( hashCodeSection ));
936+ assertFalse ("Inherited external docs should not retain raw Javadoc comment markers (a leading '* ') in:\n " + doc ,
937+ Pattern . compile ( " \\ * \\ s+(?i: Removes|Returns|Indicates|Creates|Closes|Flushes) \\ b" ). matcher ( doc ). find ( ));
932938 assertFalse ("Inherited external docs should not leave raw link/index inline tags in:\n " + doc ,
933939 doc .contains ("{@linkplain" ) || doc .contains ("{@index" ));
934940 assertFalse ("External Map/Object inheritDoc should not remain literal in:\n " + doc ,
@@ -1051,6 +1057,31 @@ private static String normalizeWhitespace(String text) {
10511057 return text == null ? null : text .replaceAll ("\\ s+" , " " ).trim ();
10521058 }
10531059
1060+ private static void assertMatches (String message , String regex , String content ) {
1061+ if (content == null || !Pattern .compile (regex ).matcher (content ).find ()) {
1062+ fail (message + "\n pattern: " + regex + "\n content: " + content );
1063+ }
1064+ }
1065+
1066+ private boolean skipIfNoJdkSrcZip () {
1067+ if (jdkSrcZipPath () != null ) return false ;
1068+ System .out .println ("[skip] " + getName () + ": JDK src.zip not found under java.home="
1069+ + System .getProperty ("java.home" ));
1070+ return true ;
1071+ }
1072+
1073+ private static Path jdkSrcZipPath () {
1074+ String javaHome = System .getProperty ("java.home" );
1075+ if (javaHome == null || javaHome .isEmpty ()) return null ;
1076+ Path home = Paths .get (javaHome );
1077+ Path direct = home .resolve ("lib/src.zip" );
1078+ if (Files .isRegularFile (direct )) return direct ;
1079+ Path parent = home .getParent ();
1080+ if (parent == null ) return null ;
1081+ Path sibling = parent .resolve ("lib/src.zip" );
1082+ return Files .isRegularFile (sibling ) ? sibling : null ;
1083+ }
1084+
10541085 private static GroovyMethodDoc findMethod (GroovyClassDoc classDoc , String name , int parameterCount ) {
10551086 for (GroovyMethodDoc method : classDoc .methods ()) {
10561087 if (name .equals (method .name ()) && method .parameters ().length == parameterCount ) {
@@ -3448,4 +3479,88 @@ private GroovyClassDoc getGroovyClassDocByName(GroovyRootDoc root, String name)
34483479
34493480 return null ;
34503481 }
3482+
3483+ public void testEraseTypeNameStripsGenericsAndWildcards () {
3484+ assertEquals ("List" , ExternalJavadocSupport .eraseTypeName ("List<String>" ));
3485+ assertEquals ("Map" , ExternalJavadocSupport .eraseTypeName ("Map<String, Integer>" ));
3486+ assertEquals ("Map" , ExternalJavadocSupport .eraseTypeName ("Map<String, Map<String, Integer>>" ));
3487+ assertEquals ("Number" , ExternalJavadocSupport .eraseTypeName ("? extends Number" ));
3488+ assertEquals ("Integer" , ExternalJavadocSupport .eraseTypeName ("? super Integer" ));
3489+ assertEquals ("Object" , ExternalJavadocSupport .eraseTypeName ("?" ));
3490+ assertEquals ("List[]" , ExternalJavadocSupport .eraseTypeName ("List<? extends T>[]" ));
3491+ assertEquals ("" , ExternalJavadocSupport .eraseTypeName ("" ));
3492+ assertEquals ("" , ExternalJavadocSupport .eraseTypeName (null ));
3493+ }
3494+
3495+ public void testFindFallbackEntryFindsByTrailingPathSuffix () throws Exception {
3496+ Path zipPath = Files .createTempFile ("ext-javadoc-fallback" , ".zip" );
3497+ try {
3498+ try (ZipOutputStream out = new ZipOutputStream (Files .newOutputStream (zipPath ))) {
3499+ out .putNextEntry (new ZipEntry ("noise/elsewhere/Other.java" ));
3500+ out .write ("// other" .getBytes ());
3501+ out .closeEntry ();
3502+ out .putNextEntry (new ZipEntry ("some.module/com/example/Foo.java" ));
3503+ out .write ("// foo" .getBytes ());
3504+ out .closeEntry ();
3505+ }
3506+ try (ZipFile zip = new ZipFile (zipPath .toFile ())) {
3507+ ZipEntry hit = ExternalJavadocSupport .findFallbackEntry (zip , "java.base/com/example/Foo.java" );
3508+ assertNotNull ("Expected fallback to find Foo.java by trailing path" , hit );
3509+ assertEquals ("some.module/com/example/Foo.java" , hit .getName ());
3510+
3511+ ZipEntry miss = ExternalJavadocSupport .findFallbackEntry (zip , "java.base/com/example/Missing.java" );
3512+ assertNull ("No entry should match Missing.java" , miss );
3513+ }
3514+ } finally {
3515+ Files .deleteIfExists (zipPath );
3516+ }
3517+ }
3518+
3519+ public void testFindFallbackEntryHandlesEntryNameWithoutSlash () throws Exception {
3520+ Path zipPath = Files .createTempFile ("ext-javadoc-fallback-noslash" , ".zip" );
3521+ try {
3522+ try (ZipOutputStream out = new ZipOutputStream (Files .newOutputStream (zipPath ))) {
3523+ out .putNextEntry (new ZipEntry ("any/where/Bare.java" ));
3524+ out .write ("// bare" .getBytes ());
3525+ out .closeEntry ();
3526+ }
3527+ try (ZipFile zip = new ZipFile (zipPath .toFile ())) {
3528+ ZipEntry hit = ExternalJavadocSupport .findFallbackEntry (zip , "Bare.java" );
3529+ assertNotNull ("Expected fallback to find entry by '/Bare.java' suffix" , hit );
3530+ assertEquals ("any/where/Bare.java" , hit .getName ());
3531+ }
3532+ } finally {
3533+ Files .deleteIfExists (zipPath );
3534+ }
3535+ }
3536+
3537+ // Exercises the matchesTypeName same-package / java.lang / array-aware branches: the JDK
3538+ // source for java.lang.Throwable declares getMessage() returning String, setStackTrace
3539+ // taking StackTraceElement[] (bare same-package types). Override-matching has to fall
3540+ // through past canonical/typeName checks before resolving these to the reflected
3541+ // java.lang.* qualified types and producing a non-empty raw comment.
3542+ public void testMatchesTypeNameResolvesUnqualifiedJavaLangTypes () {
3543+ if (skipIfNoJdkSrcZip ()) return ;
3544+ try (AutoCloseable ignored = ExternalJavadocSupport .openCacheSession ()) {
3545+ GroovyMethodDoc [] docs = ExternalJavadocSupport .methodsFor (new ExternalGroovyClassDoc (Throwable .class ));
3546+ assertTrue ("Expected reflective methods for java.lang.Throwable" , docs .length > 0 );
3547+
3548+ boolean foundGetMessage = false ;
3549+ boolean foundSetStackTrace = false ;
3550+ for (GroovyMethodDoc doc : docs ) {
3551+ String raw = ((SimpleGroovyMethodDoc ) doc ).getRawCommentText ();
3552+ if (raw == null || raw .isEmpty ()) continue ;
3553+ if ("getMessage" .equals (doc .name ()) && doc .parameters ().length == 0 ) {
3554+ foundGetMessage = true ;
3555+ } else if ("setStackTrace" .equals (doc .name ()) && doc .parameters ().length == 1 ) {
3556+ foundSetStackTrace = true ;
3557+ }
3558+ }
3559+ assertTrue ("Expected resolved Javadoc for Throwable#getMessage()" , foundGetMessage );
3560+ assertTrue ("Expected resolved Javadoc for Throwable#setStackTrace(StackTraceElement[])"
3561+ + " (bare same-package array parameter)" , foundSetStackTrace );
3562+ } catch (Exception e ) {
3563+ fail ("session close threw: " + e );
3564+ }
3565+ }
34513566}
0 commit comments