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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1a56ca708f89 CAMEL-25814: camel-jbang - the AI validator compiles a 
Java file with the other Java files of the project (#28103)
1a56ca708f89 is described below

commit 1a56ca708f8971270b99b43b60f17b07e83d2ae8
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 19 21:24:39 2026 +0200

    CAMEL-25814: camel-jbang - the AI validator compiles a Java file with the 
other Java files of the project (#28103)
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../dsl/jbang/core/commands/ai/JavaChecks.java     |  67 ++++++++++-
 .../jbang/core/commands/ai/SourceValidator.java    |   2 +-
 .../core/commands/ai/JavaChecksProjectTest.java    | 127 +++++++++++++++++++++
 3 files changed, 192 insertions(+), 4 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecks.java
index 33704254b306..e05177d696d8 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecks.java
@@ -43,6 +43,15 @@ final class JavaChecks {
      * its line. Returns nothing when no compiler is available (a JRE).
      */
     public static List<String> validateJava(String fileName, String content) {
+        return validateJava(fileName, content, null);
+    }
+
+    /**
+     * As {@link #validateJava(String, String)}, together with the other Java 
files of the project in the directory, as
+     * {@code camel run} compiles them: a class that uses another class of the 
project compiles, where on its own it was
+     * refused with "cannot find symbol" (CAMEL-25814). Only the diagnostics 
of this file are reported.
+     */
+    public static List<String> validateJava(String fileName, String content, 
Path directory) {
         List<String> msgs = new ArrayList<>();
         javax.tools.JavaCompiler compiler = 
javax.tools.ToolProvider.getSystemJavaCompiler();
         if (compiler == null || content == null || content.isBlank()) {
@@ -64,11 +73,18 @@ final class JavaChecks {
         javax.tools.DiagnosticCollector<javax.tools.JavaFileObject> 
diagnostics = new javax.tools.DiagnosticCollector<>();
         try {
             Path out = Files.createTempDirectory("camel-validate-java");
-            try {
+            try (javax.tools.StandardJavaFileManager files
+                    = compiler.getStandardFileManager(null, Locale.ENGLISH, 
java.nio.charset.StandardCharsets.UTF_8)) {
                 List<String> options = new ArrayList<>(
                         List.of("-proc:none", "-Xlint:none", "-d", 
out.toString(),
                                 "-classpath", 
System.getProperty("java.class.path", "")));
-                compiler.getTask(null, null, diagnostics, options, null, 
List.of(source)).call();
+                List<javax.tools.JavaFileObject> units = new ArrayList<>();
+                units.add(source);
+                for (javax.tools.JavaFileObject sibling : 
files.getJavaFileObjectsFromPaths(
+                        siblingSources(directory, fileName, pkg, className))) {
+                    units.add(sibling);
+                }
+                compiler.getTask(null, files, diagnostics, options, null, 
units).call();
             } finally {
                 try (var walk = Files.walk(out)) {
                     walk.sorted(java.util.Comparator.reverseOrder()).forEach(f 
-> f.toFile().delete());
@@ -79,7 +95,8 @@ final class JavaChecks {
         }
         String[] lines = content.split("\n", -1);
         for (var d : diagnostics.getDiagnostics()) {
-            if (d.getKind() == javax.tools.Diagnostic.Kind.ERROR) {
+            // the errors of this file: another file of the project may not 
compile, which is not this file's problem
+            if (d.getKind() == javax.tools.Diagnostic.Kind.ERROR && 
(d.getSource() == null || d.getSource() == source)) {
                 String text = d.getMessage(Locale.ENGLISH).replace("\n", " 
").trim();
                 long ln = d.getLineNumber();
                 if ((text.contains("expected") || text.startsWith("illegal 
character"))
@@ -168,6 +185,50 @@ final class JavaChecks {
         return msgs;
     }
 
+    /**
+     * The other Java files of the project, which {@code camel run} compiles 
with the file: those in the directory and
+     * in {@code src/main/java}, as the bean declarations are read. The file 
itself, and another file of the same class,
+     * are left out, as the content being validated replaces them.
+     */
+    static List<Path> siblingSources(Path directory, String fileName, String 
pkg, String className) {
+        List<Path> answer = new ArrayList<>();
+        if (directory == null || !Files.isDirectory(directory)) {
+            return answer;
+        }
+        Path self = directory.resolve(fileName).normalize();
+        String fqcn = pkg != null ? pkg + "." + className : className;
+        try {
+            List<Path> files = new ArrayList<>();
+            try (var list = Files.list(directory)) {
+                list.filter(Files::isRegularFile).forEach(files::add);
+            }
+            Path maven = directory.resolve("src/main/java");
+            if (Files.isDirectory(maven)) {
+                try (var walk = Files.walk(maven)) {
+                    walk.filter(Files::isRegularFile).forEach(files::add);
+                }
+            }
+            for (Path f : files) {
+                String name = f.getFileName().toString();
+                if (!name.endsWith(".java") || f.normalize().equals(self)) {
+                    continue;
+                }
+                if (name.equals(className + ".java")) {
+                    Matcher pm = 
JAVA_PACKAGE_PATTERN.matcher(Files.readString(f));
+                    String other = pm.find() ? pm.group(1) + "." + className : 
className;
+                    if (other.equals(fqcn)) {
+                        continue;
+                    }
+                }
+                answer.add(f);
+            }
+        } catch (Exception e) {
+            // the file on its own, as before
+            return List.of();
+        }
+        return answer;
+    }
+
     static final Pattern MISSING_CLASS_PATTERN = Pattern.compile("cannot find 
symbol\\s+symbol:\\s+class (\\w+)");
 
     static final Pattern MISSING_PACKAGE_PATTERN = Pattern.compile("package 
([\\w.]+) does not exist");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
index 6378d0af3b9e..55f38efe889d 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
@@ -165,7 +165,7 @@ public final class SourceValidator {
             return validateProperties(content, catalog, extraPropertyLine);
         }
         if (name.endsWith(".java")) {
-            List<String> msgs = validateJava(fileName, content);
+            List<String> msgs = JavaChecks.validateJava(fileName, content, 
directory);
             if (directory != null && !msgs.isEmpty()) {
                 msgs = withSiblingClassHints(msgs, 
BeanDeclarations.scan(directory, fileName));
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecksProjectTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecksProjectTest.java
new file mode 100644
index 000000000000..e89cd8d96a49
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaChecksProjectTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.ai;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-25814: a Java file is compiled together with the other Java files of 
the project, as camel run compiles them,
+ * so a class that uses another class of the project is not refused.
+ */
+class JavaChecksProjectTest {
+
+    private static final String ORDER = """
+            package camel.example;
+
+            import java.util.List;
+
+            public record Order(String orderId, String customer, 
List<OrderLine> lines) {
+            }
+            """;
+
+    @TempDir
+    Path project;
+
+    @Test
+    void aClassThatUsesAnotherClassOfTheProjectCompiles() throws Exception {
+        write("src/main/java/camel/example/OrderLine.java", """
+                package camel.example;
+
+                public record OrderLine(String sku, int qty, double price) {
+                }
+                """);
+
+        
assertThat(JavaChecks.validateJava("src/main/java/camel/example/Order.java", 
ORDER, project)).isEmpty();
+        // on its own, as before: the class of the other file is not found
+        
assertThat(JavaChecks.validateJava("src/main/java/camel/example/Order.java", 
ORDER)).isNotEmpty()
+                .first().asString().contains("cannot find symbol");
+    }
+
+    @Test
+    void aClassNextToTheFileCompilesToo() throws Exception {
+        write("OrderLine.java", """
+                public record OrderLine(String sku, int qty) {
+                }
+                """);
+
+        assertThat(JavaChecks.validateJava("Order.java", """
+                import java.util.List;
+
+                public record Order(String orderId, List<OrderLine> lines) {
+                }
+                """, project)).isEmpty();
+    }
+
+    @Test
+    void theErrorsOfAnotherFileAreNotThisFilesProblem() throws Exception {
+        write("src/main/java/camel/example/OrderLine.java", """
+                package camel.example;
+
+                public record OrderLine(String sku, int qty, double price) {
+                }
+                """);
+        write("src/main/java/camel/example/Broken.java", """
+                package camel.example;
+
+                public class Broken {
+                    void x() { int i = "text" }
+                }
+                """);
+
+        
assertThat(JavaChecks.validateJava("src/main/java/camel/example/Order.java", 
ORDER, project)).isEmpty();
+    }
+
+    @Test
+    void theFileOnDiskIsReplacedByTheContent() throws Exception {
+        // the version on disk is the one being rewritten: it is no second 
class of the same name
+        write("src/main/java/camel/example/Order.java", """
+                package camel.example;
+
+                public class Order {
+                    private String orderId;
+                }
+                """);
+        write("src/main/java/camel/example/OrderLine.java", """
+                package camel.example;
+
+                public record OrderLine(String sku, int qty, double price) {
+                }
+                """);
+
+        
assertThat(JavaChecks.validateJava("src/main/java/camel/example/Order.java", 
ORDER, project)).isEmpty();
+    }
+
+    @Test
+    void aClassThatIsNowhereIsStillReported() {
+        List<String> msgs = 
JavaChecks.validateJava("src/main/java/camel/example/Order.java", ORDER, 
project);
+        assertThat(msgs).isNotEmpty();
+        assertThat(msgs.get(0)).contains("cannot find 
symbol").contains("OrderLine");
+    }
+
+    private void write(String file, String content) throws Exception {
+        Path p = project.resolve(file);
+        Files.createDirectories(p.getParent());
+        Files.writeString(p, content);
+    }
+}

Reply via email to