Repository: apache/netbeans
Issue: Disassembled `*.class` opened instead of `*.java` when folder with name of class exists in sources JAR

### Apache NetBeans version

Apache NetBeans 17

### What happened

When you have a Maven sources JAR present in the local repository, confirmed by `SourceForBinaryQuery` via `apisupport.projectinspector`¹, then visiting a type or member in the classpath should open the editor on a read-only source file. In NB 16 this used to work consistently. In 17 it sometimes works, but sometimes you get the disassembled bytecode instead. I am still working out the precise conditions for the bug, since it happens on some types in a single JAR file and not others.

----

¹Sadly abandoned in `contrib` Hg rather than donated, AFAICT, but I still have a personal copy.

### How to reproduce

```bash
git clone https://github.com/jenkinsci/log-cli-plugin
cd log-cli-plugin
mvn -Pquick-build package dependency:sources
```

Now open this project in NB and wait for classpath scanning to complete. At https://github.com/jenkinsci/log-cli-plugin/blob/3d86457a9f2b19567d90542074224416a9ca474c/src/main/java/org/jenkinsci/plugins/log_cli/TailLogCommand.java#L55 place the caret on the call to `get` and press Ctrl-B to visit the definition.

Expected: you should see https://github.com/jenkinsci/jenkins/blob/jenkins-2.361.4/core/src/main/java/jenkins/model/Jenkins.java#L810

Actual: you will see disassembled bytecode

```java
    @NonNull
    public static Jenkins get() throws IllegalStateException {
        // <editor-fold defaultstate="collapsed" desc="Compiled Code">
        /* 0: invokestatic  #8                  // Method getInstanceOrNull:()Ljenkins/model/Jenkins;
         * 3: astore_0
         * 4: aload_0
         * 5: ifnonnull     18
         * 8: new           #9                  // class java/lang/IllegalStateException
         * 11: dup
         * 12: ldc           #10                 // String Jenkins.instance is missing. Read the documentation of Jenkins.getInstanceOrNull to see what you are doing wrong.
         * 14: invokespecial #11                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
         * 17: athrow
         * 18: aload_0
         * 19: areturn
         *  */
        // </editor-fold>
    }
```

Note that if on the same line of source you instead click on the next method call, `checkPermission`, you are correctly taken to https://github.com/jenkinsci/jenkins/blob/jenkins-2.361.4/core/src/main/java/hudson/security/AccessControlled.java#L48 from `~/.m2/repository/org/jenkins-ci/main/jenkins-core/2.361.4/jenkins-core-2.361.4-sources.jar`.

### Did this work correctly in an earlier version?

Apache NetBeans 16

### Operating System

Xubuntu 22.04.2

### JDK

11.0.18; OpenJDK 64-Bit Server VM 11.0.18+10-post-Ubuntu-0ubuntu122.04; Ubuntu

### Apache NetBeans packaging

Apache NetBeans Snap Package

### Anything else

Using `git bisect` I was able to identify #5152 (@jtulach) as the cause of the regression. This does _not_ appear to be a duplicate of #5266, which apparently involved a visible exception (no error is printed in this case) and which was allegedly fixed in NB 17 in #5279 (@matthiasblaesing).

I hope to find time to study the bug and offer a PR, since this affects everyday usage of the IDE for me and is the one noticeable regression in NB 17.

### Are you willing to submit a pull request?

Yes

Changed files:
java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTest.java

diff --git a/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java b/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
index e58a6a257c..dfb02790c6 100644
--- a/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
+++ b/java/java.source.base/src/org/netbeans/api/java/source/SourceUtils.java
@@ -628,7 +628,9 @@ public class SourceUtils {
     private static FileObject findMatchingChild(String sourceFileName, Collection<FileObject> folders, boolean caseSensitive) {
         final Match matchSet = caseSensitive ? new CaseSensitiveMatch(sourceFileName) : new CaseInsensitiveMatch(sourceFileName);
         for (FileObject folder : folders) {
-            for (FileObject child : folder.getChildren()) {
+            FileObject[] children = folder.getChildren();
+            Arrays.sort(children, Comparator.comparing(FileObject::getNameExt)); // for determinism
+            for (FileObject child : children) {
                 if (matchSet.apply(child)) {
                     return child;
                 }
@@ -684,6 +686,9 @@ public class SourceUtils {
         }
 
         final boolean apply(final FileObject fo) {
+            if (fo.isFolder()) {
+                return false;
+            }
             if (fo.getNameExt().equals(name)) {
                 return true;
             }
@@ -1127,9 +1132,8 @@ public class SourceUtils {
     }
 
     /**
-     * Returns candidate filenames given a classname. The return value is either
-     * a String (top-level class, no $) or List&lt;String> as the JLS permits $ in
-     * class names.
+     * Returns candidate filenames given a classname.
+     * @return a single name (top-level class, no $) or multiple as the JLS permits $ in class names.
      */
     private static List<String> getSourceFileNames(String classFileName) {
         int index = classFileName.lastIndexOf('$');
diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTest.java
index 0a12ba30d8..90da3063a1 100644
--- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTest.java
+++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/SourceUtilsTest.java
@@ -321,6 +321,7 @@ public class SourceUtilsTest extends ClassIndexTestCase {
         FileObject srcInDefPkg = src.createData("Foo","java");
         assertNotNull(srcInDefPkg);
         FileObject sourceFile = src.createFolder("org").createFolder("me").createData("Test", "java");
+        src.getFileObject("org/me").createFolder("Test"); // https://github.com/apache/netbeans/issues/5738
         assertNotNull(sourceFile);
         ClasspathInfo cpInfo = ClasspathInfo.create(ClassPathSupport.createClassPath(new FileObject[0]), ClassPathSupport.createClassPath(new FileObject[0]),
             ClassPathSupport.createClassPath(new FileObject[]{src}));
@@ -337,16 +338,16 @@ public class SourceUtilsTest extends ClassIndexTestCase {
         ElementHandle<? extends Element> handle = ElementHandle.createTypeElementHandle(ElementKind.CLASS, "org.me.Test");
         assertNotNull (handle);
         FileObject result = SourceUtils.getFile(handle, cpInfo);
-        assertNotNull(result);
+        assertEquals(sourceFile, result);
         handle = ElementHandle.createTypeElementHandle(ElementKind.CLASS, "org.me.Test$Inner");
         result = SourceUtils.getFile(handle,cpInfo);
-        assertNotNull(result);
+        assertEquals(sourceFile, result);
         handle = ElementHandle.createPackageElementHandle("org.me");
         result = SourceUtils.getFile(handle,cpInfo);
-        assertNotNull(result);
+        assertEquals(sourceFile.getParent(), result);
         handle = ElementHandle.createTypeElementHandle(ElementKind.CLASS, "Foo");
         result = SourceUtils.getFile(handle,cpInfo);
-        assertNotNull(result);
+        assertEquals(srcInDefPkg, result);
     }
 
     public void testGetMainClasses() throws Exception {