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);
+ }
+}