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 67e73b3cb425 chore: camel-jbang - source editor: a note for a direct:
or seda: endpoint no route consumes on Java and XML routes too
67e73b3cb425 is described below
commit 67e73b3cb4256e075776288d843fa378ca5a3a1d
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 23 12:52:01 2026 +0200
chore: camel-jbang - source editor: a note for a direct: or seda: endpoint
no route consumes on Java and XML routes too
Java and XML route files now get the same note as YAML ones: a line that
sends
to a direct: or seda: endpoint no route of the project consumes is marked
with
a warning that does not block the save.
The checks of a file move from SourceViewer to SourceChecks, which holds the
validators and runs one sequence (the YAML one was written twice, for a save
and for typing). What it finds is a Found of errors and notes, which also
says
whether the errors keep the file from being saved (YAML, properties and XML,
not Java). A save still checks the YAML DSL schema, and leaves out the
notes of
a file whose errors keep it from being saved. The viewer has one notes hook
for
every route file (setNotes), set each time a file opens. SourceViewer
shrinks
from 3055 to 2953 lines.
camel-jbang-core has EndpointConsumerChecks.validateRouteConsumers, which
runs
only the consumer check of ModelChecks against the endpoints the other route
files consume, the set ConsumedEndpoints keeps.
Found in a live run: the other route files are now read again each time a
file
opens (the set read before is shown meanwhile), and when a read finds other
endpoints the editor checks its text at once (SourceViewer.recheck) rather
than
at the next change. The F1 help of the Source tab says what a note is.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../ROOT/pages/camel-jbang-tui-source-editor.adoc | 12 +-
.../core/commands/ai/EndpointConsumerChecks.java | 23 +++
.../dsl/jbang/core/commands/ai/ModelChecks.java | 13 ++
.../ai/SourceValidatorEndpointConsumersTest.java | 36 ++++
dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md | 2 +-
.../jbang/core/commands/tui/ConsumedEndpoints.java | 35 +++-
.../dsl/jbang/core/commands/tui/SourceChecks.java | 181 ++++++++++++++++++
.../jbang/core/commands/tui/SourceEditAssist.java | 6 +-
.../dsl/jbang/core/commands/tui/SourceTab.java | 15 +-
.../dsl/jbang/core/commands/tui/SourceViewer.java | 202 +++++----------------
.../src/main/resources/tui/help/source.md | 4 +
.../jbang/core/commands/tui/SourceChecksTest.java | 99 ++++++++++
.../core/commands/tui/SourceConsumerNotesTest.java | 98 ++++++++--
.../commands/tui/SourceKameletValidationTest.java | 2 +-
.../jbang/core/commands/tui/SourceSizeTest.java | 2 +-
15 files changed, 542 insertions(+), 188 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
index 0de33d4d0efc..b020d717ec43 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
@@ -90,12 +90,12 @@ panel at the bottom says what is wrong with the line the
cursor is on. A note, s
counted apart in the title as `notes`. A note does not keep the file from
being saved. They are checked again as you type, and *F9*
jumps to the next problem.
-A YAML route that sends to a `direct` or `seda` endpoint no route consumes
gets a note on that line, such as
-`to: direct:cheese` while no route has `from: direct:cheese`. The routes of
the other YAML, Java and XML files of the
-project count too. They are read in the background when the file opens, and
read again every few seconds. It is a
-note rather than an error, as the route that consumes it is often the next one
you write. The check says nothing when
-the other files cannot tell, such as a route whose input is a property
placeholder or a route template, or a project
-with more files than the check reads.
+A route that sends to a `direct` or `seda` endpoint no route consumes gets a
note on that line, such as
+`to: direct:cheese` while no route has `from: direct:cheese`. This works for
YAML, Java and XML routes, and the routes
+of the other YAML, Java and XML files of the project count too. They are read
in the background each time a file
+opens, and again while you type once the last read is a few seconds old. It is
a note rather than an error, as the
+route that consumes it is often the next one you write. The check says nothing
when the other files cannot tell, such as a route whose input is a property
+placeholder or a route template, or a project with more files than the check
reads.
image::jbang/camel-tui-source-problems.png[A misspelled seda option marked
while typing]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
index 1d39f6fb9ae2..fd8f4277bc74 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/EndpointConsumerChecks.java
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -97,6 +98,28 @@ public final class EndpointConsumerChecks {
return answer;
}
+ /**
+ * The Java and XML DSL twin of {@link #validateYamlConsumers(String,
Set)}: the direct: and seda: endpoints the
+ * routes of the source send to and no route consumes, as "Line N:
message" on the first line that sends to each.
+ *
+ * @param javaSources the Java sources of the project, for the
constants a route refers to in another class
+ * @param consumedElsewhere what {@link #consumed(Path, String,
CamelCatalog)} returned; null says nothing
+ * @return the messages; none for a file that is not a
Java or XML DSL source
+ */
+ public static List<String> validateRouteConsumers(
+ String fileName, String content, CamelCatalog catalog, Map<String,
Supplier<String>> javaSources,
+ Set<String> consumedElsewhere) {
+ if (consumedElsewhere == null) {
+ return List.of();
+ }
+ RouteModel model = RouteModel.read(fileName, content, catalog,
javaSources);
+ if (model == null) {
+ return List.of();
+ }
+ List<RouteNodes.Node> nodes =
RouteNodes.withExpressionLines(RouteNodes.of(model, catalog), content);
+ return ModelChecks.consumers(model, nodes, content, catalog,
consumedElsewhere);
+ }
+
/** The lines, from 0, that name the endpoint (direct:cheese, also as
direct://cheese), comments left out. */
private static List<Integer> linesNaming(String[] lines, String endpoint) {
int colon = endpoint.indexOf(':');
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
index 6ee6897bd72d..21ef8aa173cc 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ModelChecks.java
@@ -81,6 +81,19 @@ final class ModelChecks {
return checks.errors;
}
+ /**
+ * Only the direct: and seda: endpoints the routes send to and no route
consumes, for an editor that marks them
+ * apart from the other problems: as notes, which do not keep a file from
being saved.
+ *
+ * @param consumedElsewhere the endpoints the other route files consume;
null says nothing
+ */
+ static List<String> consumers(
+ RouteModel model, List<Node> nodes, String content, CamelCatalog
catalog, Set<String> consumedElsewhere) {
+ ModelChecks checks = new ModelChecks(model, nodes, content, catalog);
+ checks.consumers(consumedElsewhere);
+ return checks.errors;
+ }
+
private void endpoint(Node n) {
String uri = n.uri() != null ? n.uri().strip() : null;
if (uri == null || uri.isEmpty() || RouteModel.hasUnknownValue(uri) ||
uri.startsWith("{{")) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
index 640e42bb9790..cc7262700a52 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointConsumersTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands.ai;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.apache.camel.catalog.CamelCatalog;
@@ -314,4 +315,39 @@ class SourceValidatorEndpointConsumersTest {
// the other files not known: nothing is said
assertThat(EndpointConsumerChecks.validateYamlConsumers(route,
null)).isEmpty();
}
+
+ @Test
+ void theEditorGetsTheMessageOfAJavaOrXmlRouteOnItsLine() {
+ String java = """
+ import org.apache.camel.builder.RouteBuilder;
+
+ public class Tick extends RouteBuilder {
+ @Override
+ public void configure() {
+ from("timer:tick")
+ .to("direct:cheese")
+ .to("direct:other");
+ }
+ }
+ """;
+ Set<String> consumed = Set.of("direct:other");
+ assertThat(EndpointConsumerChecks.validateRouteConsumers("Tick.java",
java, CATALOG, Map.of(), consumed))
+ .singleElement().asString()
+ .startsWith("Line 7: sends to direct:cheese, and no route
consumes it")
+ .endsWith("add a route with from(\"direct:cheese\"), or
correct the name");
+ String xml = """
+ <routes>
+ <route>
+ <from uri="timer:tick"/>
+ <to uri="direct:cheese"/>
+ </route>
+ </routes>
+ """;
+
assertThat(EndpointConsumerChecks.validateRouteConsumers("tick.camel.xml", xml,
CATALOG, Map.of(), consumed))
+ .singleElement().asString().startsWith("Line 4: sends to
direct:cheese");
+ // the other files not known, or a file that is not a route source:
nothing is said
+ assertThat(EndpointConsumerChecks.validateRouteConsumers("Tick.java",
java, CATALOG, Map.of(), null)).isEmpty();
+ assertThat(EndpointConsumerChecks.validateRouteConsumers("pom.xml",
"<project/>", CATALOG, Map.of(), consumed))
+ .isEmpty();
+ }
}
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
index 412984c081b5..1b4095abd581 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/AGENTS.md
@@ -32,7 +32,7 @@ and use each other's package-private members, so there are no
sub-packages. Clas
| `DataRefreshService`, `StatusFileReader`, `StatusParser` | read the status
files of the integrations (`~/.camel/<pid>-status.json`) into `IntegrationInfo`
and the `*Info` classes |
| `*Popup`, `PopupManager` | popups; `PopupManager` has the switch integration
(F3), More and confirm popups |
| `FilterListPopup`, `ChoicePopup`, `ActivityLogPopup`, `DialogHelper` | what
popups are built on: a list narrowed down by typing, a short list of choices, a
log with a detail panel, and centered confirm and input dialogs |
-| `SourceTab`, `SourceViewer`, `SourceEditAssist`, `*Completions`, `*QuickDoc`
| the source editor; its Tab completion is `SourceCompletion`, its Ctrl+R
refactorings `SourceRefactorActions`, the live run data at the end of the lines
`SourceLiveColumn` |
+| `SourceTab`, `SourceViewer`, `SourceEditAssist`, `*Completions`, `*QuickDoc`
| the source editor; its Tab completion is `SourceCompletion`, its Ctrl+R
refactorings `SourceRefactorActions`, the live run data at the end of the lines
`SourceLiveColumn`, the checks of a file (errors, and notes that do not block a
save) `SourceChecks` |
| `DiagramSupport`, `DiagramDetailSupport`, `ArchitectureView`, `diagram.*` |
route and architecture diagrams; what they draw comes from `DiagramTopology`
(`ctx.diagramTopology`), which asks an integration for the structure of its
routes only when they change and takes the numbers from the status file |
| `AbstractDiagramTab` | what the Diagram and Routes tabs share of the route
diagram: its state, keys, drilling into a route and back |
| `AiPanel`, `AiSlashCommandRegistry` | the F8 AI panel; its input line is
`AiInputLine`, its tools and system prompt `AiToolSet`, its usage statistics
`AiUsageStats`, and an external coding agent (ACP) `AcpSession` over
`AcpAgentClient` |
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
index 7cbb40507c22..c0e5c7ea8b53 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumedEndpoints.java
@@ -18,10 +18,13 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
import java.nio.file.Path;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.dsl.jbang.core.commands.ai.EndpointConsumerChecks;
@@ -59,20 +62,28 @@ final class ConsumedEndpoints {
}
/**
- * The notes of a YAML route file: each line that sends to a direct: or
seda: endpoint no route of the project
- * consumes, as "Line N: message"; none while the other route files are
being read the first time.
+ * The notes of a YAML, Java or XML route file: each direct: or seda:
endpoint it sends to that no route of the
+ * project consumes, as "Line N: message"; none while the other route
files are being read the first time.
*
- * @param rootDir the project directory, whose route files are read; the
file's own directory when it is not in it
- * @param onChange called on the UI thread when a read finds other
endpoints, to mark the file again
+ * @param rootDir the project directory, whose route files are read;
the file's own directory when it is not in
+ * it
+ * @param javaSources the Java sources of the project, for the constants a
Java route refers to in another class
+ * @param onChange called on the UI thread when a read finds other
endpoints, to mark the file again
*/
- List<String> notes(Path file, Path rootDir, String content, CamelCatalog
catalog, Runnable onChange) {
+ List<String> notes(
+ Path file, Path rootDir, String content, CamelCatalog catalog,
Map<String, Supplier<String>> javaSources,
+ Runnable onChange) {
Path f = file.toAbsolutePath().normalize();
Path root = rootDir != null ? rootDir.toAbsolutePath().normalize() :
null;
if (root == null || !f.startsWith(root)) {
root = f.getParent();
}
- return EndpointConsumerChecks.validateYamlConsumers(content,
- get(root, root.relativize(f).toString(), catalog, onChange));
+ Set<String> consumed = get(root, root.relativize(f).toString(),
catalog, onChange);
+ String name = f.getFileName().toString();
+ String lower = name.toLowerCase(Locale.ROOT);
+ return lower.endsWith(".yaml") || lower.endsWith(".yml")
+ ? EndpointConsumerChecks.validateYamlConsumers(content,
consumed)
+ : EndpointConsumerChecks.validateRouteConsumers(name, content,
catalog, javaSources, consumed);
}
/**
@@ -98,6 +109,16 @@ final class ConsumedEndpoints {
return consumed;
}
+ /**
+ * Has the next {@link #get} read the files again, keeping what was read
meanwhile: a file was just opened, and the
+ * other files may have changed since they were read, in the TUI or
outside it.
+ */
+ synchronized void expire() {
+ if (readTime != 0) {
+ readTime = 1;
+ }
+ }
+
/** Reads the endpoints of the file asked about last; the caller holds the
lock. */
private void startRead() {
reading = true;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
new file mode 100644
index 000000000000..7d5c9141b2a6
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
@@ -0,0 +1,181 @@
+/*
+ * 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.tui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.networknt.schema.Error;
+import org.apache.camel.dsl.yaml.validator.YamlValidator;
+
+/**
+ * The checks of the file in the source editor, by file type: what they find
is either an error or a note. An error of a
+ * YAML, properties or XML file keeps it from being saved; a Java file is the
application's code, and is always saved
+ * with its errors said. A note is something that works but is not right:
marked and said, never blocking a save.
+ * <p/>
+ * The same checks run when a file opens, while typing and on save. A save
also checks a YAML file against the YAML DSL
+ * schema, and leaves out the notes of a file whose errors keep it from being
saved.
+ */
+final class SourceChecks {
+
+ /** The kind of file, which decides the checks and whether an error blocks
the save. */
+ enum Kind {
+ YAML,
+ PROPERTIES,
+ XML,
+ /** a Java route file, or a file the route checks do not read */
+ OTHER
+ }
+
+ /** What the checks found, each as "Line N: message", or a message of no
line. */
+ record Found(List<String> errors, List<String> notes) {
+
+ static final Found NONE = new Found(List.of(), List.of());
+
+ /** The errors then the notes. */
+ List<String> all() {
+ if (notes.isEmpty()) {
+ return errors;
+ }
+ List<String> answer = new ArrayList<>(errors);
+ answer.addAll(notes);
+ return answer;
+ }
+
+ /** The problem of each line, by line from 0; a line with an error and
a note shows the error. */
+ Map<Integer, String> byLine(String content) {
+ return SourceValidationSupport.buildInlineErrors(all(), content);
+ }
+
+ /** The lines, from 0, whose problem is a note. */
+ Set<Integer> noteLines(String content) {
+ if (notes.isEmpty()) {
+ return Set.of();
+ }
+ Set<Integer> answer = new
HashSet<>(SourceValidationSupport.buildInlineErrors(notes, content).keySet());
+ answer.removeAll(SourceValidationSupport.buildInlineErrors(errors,
content).keySet());
+ return answer;
+ }
+
+ /** Whether the errors keep a file of the kind from being saved. */
+ boolean blocksSave(Kind kind) {
+ return !errors.isEmpty() && kind != Kind.OTHER;
+ }
+ }
+
+ private YamlValidator yamlValidator;
+ private SourceViewer.PropertiesValidator propertiesValidator;
+ private SourceViewer.EndpointValidator endpointValidator;
+ private SourceViewer.EndpointValidator simpleValidator;
+ // the Kamelet checks of a YAML file (CAMEL-25411)
+ private SourceViewer.EndpointValidator kameletValidator;
+ // the Camel checks of a Java or XML DSL route file (CAMEL-25208)
+ private SourceViewer.EndpointValidator routeValidator;
+ // the notes of a YAML, Java or XML route file
+ private SourceViewer.EndpointValidator notes;
+
+ void setPropertiesValidator(SourceViewer.PropertiesValidator
propertiesValidator) {
+ this.propertiesValidator = propertiesValidator;
+ }
+
+ void setEndpointValidator(SourceViewer.EndpointValidator
endpointValidator) {
+ this.endpointValidator = endpointValidator;
+ }
+
+ void setSimpleValidator(SourceViewer.EndpointValidator simpleValidator) {
+ this.simpleValidator = simpleValidator;
+ }
+
+ void setKameletValidator(SourceViewer.EndpointValidator kameletValidator) {
+ this.kameletValidator = kameletValidator;
+ }
+
+ void setNotes(SourceViewer.EndpointValidator notes) {
+ this.notes = notes;
+ }
+
+ void setRouteValidator(SourceViewer.EndpointValidator routeValidator) {
+ this.routeValidator = routeValidator;
+ }
+
+ /** Forgets the checks of the file shown. */
+ void clear() {
+ propertiesValidator = null;
+ endpointValidator = null;
+ simpleValidator = null;
+ kameletValidator = null;
+ routeValidator = null;
+ notes = null;
+ }
+
+ /** The checks of a file just opened, or being typed. */
+ Found check(Kind kind, String content) {
+ return check(kind, content, false);
+ }
+
+ /** The checks of a file being saved: a YAML file is checked against the
YAML DSL schema too. */
+ Found checkOnSave(Kind kind, String content) {
+ return check(kind, content, true);
+ }
+
+ private Found check(Kind kind, String content, boolean save) {
+ List<String> errors = new ArrayList<>();
+ if (kind == Kind.YAML) {
+ if (save) {
+
errors.addAll(SourceValidationSupport.formatSchemaErrors(validateYaml(content),
content));
+ }
+ addAll(errors, endpointValidator, content);
+ addAll(errors, simpleValidator, content);
+ addAll(errors, kameletValidator, content);
+ } else if (kind == Kind.PROPERTIES) {
+ if (propertiesValidator != null) {
+
errors.addAll(SourceValidationSupport.validatePropertiesLines(content,
propertiesValidator::validate));
+ }
+ return errors.isEmpty() ? Found.NONE : new Found(errors,
List.of());
+ } else {
+ addAll(errors, routeValidator, content);
+ }
+ List<String> found = new ArrayList<>();
+ if (!save || !new Found(errors, List.of()).blocksSave(kind)) {
+ addAll(found, notes, content);
+ }
+ return errors.isEmpty() && found.isEmpty() ? Found.NONE : new
Found(errors, found);
+ }
+
+ private static void addAll(List<String> msgs,
SourceViewer.EndpointValidator validator, String content) {
+ if (validator != null) {
+ List<String> found = validator.validate(content);
+ if (found != null) {
+ msgs.addAll(found);
+ }
+ }
+ }
+
+ private List<Error> validateYaml(String content) {
+ try {
+ if (yamlValidator == null) {
+ yamlValidator = new YamlValidator();
+ }
+ return yamlValidator.validate(content);
+ } catch (Exception e) {
+ return List.of();
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
index bf939d460254..0950b9dec720 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
@@ -1737,8 +1737,8 @@ final class SourceEditAssist {
/**
* The Camel checks of a Java or XML DSL route file (CAMEL-25208): the
endpoint uris and simple expressions the
- * compiler cannot see, as "Line N: message". The endpoints no route
consumes are left to camel validate: the editor
- * runs this while typing, and would read the whole project each time.
+ * compiler cannot see, as "Line N: message". The endpoints no route
consumes are notes of their own, as finding
+ * them reads the whole project: ConsumedEndpoints reads it in the
background.
*/
List<String> validateRoutes(Path file, String content) {
List<String> answer = new ArrayList<>();
@@ -1826,7 +1826,7 @@ final class SourceEditAssist {
}
/** The Java sources of the project, for the constants a route refers to
in another class; kept for a while. */
- private Map<String, Supplier<String>> javaSources() {
+ Map<String, Supplier<String>> javaSources() {
long now = MonotonicClock.millis();
if (javaSourcesCache == null || now - javaSourcesCacheTime >
JAVA_SOURCES_TTL_MS
|| !Objects.equals(javaSourcesCacheDir, rootDir)) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index 0d4fa880c135..35109301f038 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -921,15 +921,24 @@ class SourceTab extends AbstractTab {
}
}
+ /** The direct: and seda: endpoints the file sends to and no route of the
project consumes, as notes. */
+ private List<String> consumerNotes(Path filePath, String content) {
+ return consumedEndpoints.notes(filePath, rootDir, content,
assist.validationCatalog(), assist.javaSources(),
+ () -> sourceViewer.recheck(filePath));
+ }
+
/**
* Points the viewer's quick-doc, autocomplete, validation and deprecation
hooks at the providers matching the file
* type (Camel YAML, other Camel source, properties, or none).
*/
private void configureEditAssist(Path filePath) {
+ consumedEndpoints.expire();
// the Camel checks of a Java or XML DSL route file (CAMEL-25208);
YAML has its own
boolean routeFile = !SourceEditAssist.isYamlFile(filePath)
&& (isJavaRouteFile(filePath) || isCamelSourceFile(filePath));
sourceViewer.setRouteValidator(routeFile ? content ->
assist.validateRoutes(filePath, content) : null);
+ // a direct: or seda: endpoint no route consumes is a note
(CAMEL-24955); a YAML file sets its notes below
+ sourceViewer.setNotes(routeFile ? content -> consumerNotes(filePath,
content) : null);
sourceViewer.setAskAi(ctx.askAiCallback);
String name = filePath.getFileName().toString();
SourceCompletion completion = sourceViewer.completion();
@@ -964,10 +973,8 @@ class SourceTab extends AbstractTab {
// the shape of a Kamelet file and the kamelet: endpoints of a
route (CAMEL-25411)
sourceViewer.setKameletValidator(content ->
SourceEditAssist.validateKamelets(filePath, content));
// the notes: a Kamelet's unused dependency, a direct: or
seda: endpoint no route consumes (CAMEL-24955)
- sourceViewer.setYamlNotes(content ->
SourceEditAssist.isKameletFile(filePath)
- ? SourceEditAssist.kameletNotes(filePath, content)
- : consumedEndpoints.notes(filePath, rootDir, content,
assist.validationCatalog(),
- () -> sourceViewer.reloadIfShowing(filePath)));
+ sourceViewer.setNotes(content ->
SourceEditAssist.isKameletFile(filePath)
+ ? SourceEditAssist.kameletNotes(filePath, content) :
consumerNotes(filePath, content));
completion.setListItemNodeChecker(assist::isListChildrenNode);
sourceViewer.setEditQuickDocProvider(withProjectDocs(assist::provideEditQuickDoc));
} else {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index 42dd66f70836..0a06ded5bda5 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -23,7 +23,6 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -34,7 +33,6 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
-import com.networknt.schema.Error;
import dev.tamboui.layout.Constraint;
import dev.tamboui.layout.Layout;
import dev.tamboui.layout.Rect;
@@ -254,16 +252,10 @@ class SourceViewer {
// the tree of the route the cursor is in, at the top right (Ctrl+T); the
setting gives the default
private boolean routeTreeShown;
private final RouteTreePanel routeTree = new RouteTreePanel();
- private org.apache.camel.dsl.yaml.validator.YamlValidator yamlValidator;
- private PropertiesValidator propertiesValidator;
- private EndpointValidator endpointValidator;
- private EndpointValidator simpleValidator;
- // the Kamelet checks of a YAML file (CAMEL-25411), and the notes of a
YAML file that do not block a save
- private EndpointValidator kameletValidator;
- private EndpointValidator yamlNotes;
+ // the checks of the file by its type: the errors, and the notes that do
not block a save
+ private final SourceChecks checks = new SourceChecks();
// the lines whose problem is a note, not an error: marked as a warning
and counted apart (CAMEL-25411)
private Set<Integer> noteLines = Set.of();
- private EndpointValidator routeValidator;
private MonitorContext.AskAi askAi;
private CursorQuickDocProvider cursorQuickDocProvider;
private List<String> routeProblems = List.of();
@@ -328,15 +320,15 @@ class SourceViewer {
}
void setPropertiesValidator(PropertiesValidator propertiesValidator) {
- this.propertiesValidator = propertiesValidator;
+ checks.setPropertiesValidator(propertiesValidator);
}
void setEndpointValidator(EndpointValidator endpointValidator) {
- this.endpointValidator = endpointValidator;
+ checks.setEndpointValidator(endpointValidator);
}
void setSimpleValidator(EndpointValidator simpleValidator) {
- this.simpleValidator = simpleValidator;
+ checks.setSimpleValidator(simpleValidator);
}
/**
@@ -345,15 +337,15 @@ class SourceViewer {
* saved, as the other problems of a YAML file.
*/
void setKameletValidator(EndpointValidator kameletValidator) {
- this.kameletValidator = kameletValidator;
+ checks.setKameletValidator(kameletValidator);
}
/**
- * What a YAML file does that works but is not right (a Kamelet's camel:
dependency its template does not use, a
- * direct: endpoint no route consumes): marked and said when saved, but
not blocking the save.
+ * What a YAML, Java or XML route file does that works but is not right (a
Kamelet's camel: dependency its template
+ * does not use, a direct: endpoint no route consumes): marked and said
when saved, but not blocking the save.
*/
- void setYamlNotes(EndpointValidator yamlNotes) {
- this.yamlNotes = yamlNotes;
+ void setNotes(EndpointValidator notes) {
+ checks.setNotes(notes);
}
/**
@@ -362,7 +354,7 @@ class SourceViewer {
* as a Java file is the application's code, and what the checks cannot
know must never keep it from being saved.
*/
void setRouteValidator(EndpointValidator routeValidator) {
- this.routeValidator = routeValidator;
+ checks.setRouteValidator(routeValidator);
}
/**
@@ -384,13 +376,8 @@ class SourceViewer {
onLineSelected = null;
deprecatedLines = Collections.emptySet();
editableFile = null;
- propertiesValidator = null;
- endpointValidator = null;
- simpleValidator = null;
- kameletValidator = null;
- yamlNotes = null;
+ checks.clear();
noteLines = Set.of();
- routeValidator = null;
cursorQuickDocProvider = null;
completion.clearLanguageProviders();
}
@@ -425,13 +412,8 @@ class SourceViewer {
loadedFilePath = null;
completion.reset();
editableFile = null;
- propertiesValidator = null;
- endpointValidator = null;
- simpleValidator = null;
- kameletValidator = null;
- yamlNotes = null;
+ checks.clear();
noteLines = Set.of();
- routeValidator = null;
cursorQuickDocProvider = null;
}
@@ -566,6 +548,19 @@ class SourceViewer {
goToLine(Math.min(line, Math.max(0, getLineCount() - 1)));
}
+ /**
+ * Checks the file shown again, as what its checks read besides the file
changed (the endpoints the other route
+ * files consume): the view is loaded again, and the editor checks its
text now, as it would after the next change.
+ */
+ void recheck(Path file) {
+ if (!editMode) {
+ reloadIfShowing(file);
+ } else if (validationErrors == null && file != null && editableFile !=
null
+ &&
editableFile.toAbsolutePath().normalize().equals(file.toAbsolutePath().normalize()))
{
+ inlineErrors = validateContent(editState.text());
+ }
+ }
+
int getLineCount() {
if (editMode) {
return editState.lineCount();
@@ -1249,62 +1244,24 @@ class SourceViewer {
}
private void validateAndNotify(String content) {
- routeProblems = List.of();
- if (validateOnSave && isCamelYamlFile()) {
- List<String> msgs = new ArrayList<>();
-
msgs.addAll(SourceValidationSupport.formatSchemaErrors(validateYaml(content),
content));
- if (endpointValidator != null) {
- List<String> endpointErrors =
endpointValidator.validate(content);
- if (endpointErrors != null) {
- msgs.addAll(endpointErrors);
- }
- }
- if (simpleValidator != null) {
- List<String> simpleErrors = simpleValidator.validate(content);
- if (simpleErrors != null) {
- msgs.addAll(simpleErrors);
- }
- }
- addAll(msgs, kameletValidator, content);
- noteLines = Set.of();
- if (!msgs.isEmpty()) {
- validationErrors = msgs;
- validationErrorScroll = 0;
- inlineErrors = buildInlineErrors(msgs, content);
- return;
- }
- List<String> notes = new ArrayList<>();
- addAll(notes, yamlNotes, content);
- if (!notes.isEmpty()) {
- // marked, and said when saved, but not blocking the save
- routeProblems = notes;
- inlineErrors = buildInlineErrors(notes, content);
- noteLines = new HashSet<>(inlineErrors.keySet());
- return;
- }
- } else if (validateOnSave && isPropertiesFile() && propertiesValidator
!= null) {
- List<String> msgs = validateProperties(content);
- if (!msgs.isEmpty()) {
- validationErrors = msgs;
- validationErrorScroll = 0;
- inlineErrors = buildInlineErrors(msgs, content);
- return;
- }
- } else if (validateOnSave && routeValidator != null) {
- List<String> msgs = routeValidator.validate(content);
- msgs = msgs != null ? msgs : List.of();
- inlineErrors = msgs.isEmpty() ? Collections.emptyMap() :
buildInlineErrors(msgs, content);
- if (isXmlFile() && !msgs.isEmpty()) {
- // XML routes as YAML routes: the problems are shown and the
file is not saved
- validationErrors = msgs;
- validationErrorScroll = 0;
- } else {
- // Java: marked, and said when saved, but not blocking the save
- routeProblems = msgs;
- }
- return;
+ SourceChecks.Found found = validateOnSave ? checks.checkOnSave(kind(),
content) : SourceChecks.Found.NONE;
+ inlineErrors = found.byLine(content);
+ noteLines = found.noteLines(content);
+ if (found.blocksSave(kind())) {
+ validationErrors = found.errors();
+ validationErrorScroll = 0;
+ routeProblems = List.of();
+ } else {
+ // a note, or the problems of a Java file: marked, and said when
saved, but not blocking the save
+ routeProblems = found.all();
}
- inlineErrors = Collections.emptyMap();
+ }
+
+ /** The kind of the file edited, for its checks. */
+ private SourceChecks.Kind kind() {
+ return isCamelYamlFile() ? SourceChecks.Kind.YAML
+ : isPropertiesFile() ? SourceChecks.Kind.PROPERTIES
+ : isXmlFile() ? SourceChecks.Kind.XML :
SourceChecks.Kind.OTHER;
}
/** The notice of a save: the file, and the Camel problems of a Java or
XML route file when it has some. */
@@ -1494,48 +1451,14 @@ class SourceViewer {
lastBackgroundValidationTime = now;
lastBackgroundValidationContent = content;
- List<String> msgs = validateContent(content);
- inlineErrors = msgs.isEmpty() ? Collections.emptyMap() :
buildInlineErrors(msgs, content);
+ inlineErrors = validateContent(content);
}
- /** The problems the checks of the file type (Camel YAML, properties, Java
or XML routes) find in the content. */
- private List<String> validateContent(String content) {
- List<String> msgs = new ArrayList<>();
- noteLines = Set.of();
- if (isCamelYamlFile()) {
- if (endpointValidator != null) {
- List<String> endpointErrors =
endpointValidator.validate(content);
- if (endpointErrors != null) {
- msgs.addAll(endpointErrors);
- }
- }
- if (simpleValidator != null) {
- List<String> simpleErrors = simpleValidator.validate(content);
- if (simpleErrors != null) {
- msgs.addAll(simpleErrors);
- }
- }
- addAll(msgs, kameletValidator, content);
- List<String> notes = new ArrayList<>();
- addAll(notes, yamlNotes, content);
- if (!notes.isEmpty()) {
- // a line with an error is marked as an error
- Set<Integer> lines = new HashSet<>(buildInlineErrors(notes,
content).keySet());
- if (!msgs.isEmpty()) {
- lines.removeAll(buildInlineErrors(msgs, content).keySet());
- }
- noteLines = lines;
- msgs.addAll(notes);
- }
- } else if (isPropertiesFile() && propertiesValidator != null) {
- msgs.addAll(validateProperties(content));
- } else if (routeValidator != null) {
- List<String> routeErrors = routeValidator.validate(content);
- if (routeErrors != null) {
- msgs.addAll(routeErrors);
- }
- }
- return msgs;
+ /** The problems the checks of the file type find in the content, by line;
the lines of the notes are kept apart. */
+ private Map<Integer, String> validateContent(String content) {
+ SourceChecks.Found found = checks.check(kind(), content);
+ noteLines = found.noteLines(content);
+ return found.byLine(content);
}
/** The title spans counting the problems of the lines: the errors, and
apart from them the notes. */
@@ -1562,23 +1485,13 @@ class SourceViewer {
return noteLines;
}
- private static void addAll(List<String> msgs, EndpointValidator validator,
String content) {
- if (validator != null) {
- List<String> found = validator.validate(content);
- if (found != null) {
- msgs.addAll(found);
- }
- }
- }
-
/** The problems of the file just loaded, by line; none when the checks
fail or do not apply. */
private Map<Integer, String> problemsOnLoad(String content) {
if (isMarkdownFile) {
return Collections.emptyMap();
}
try {
- List<String> msgs = validateContent(content);
- return msgs.isEmpty() ? Collections.emptyMap() :
buildInlineErrors(msgs, content);
+ return validateContent(content);
} catch (RuntimeException e) {
return Collections.emptyMap();
}
@@ -1612,21 +1525,6 @@ class SourceViewer {
return visible != null ? visible : inlineErrors;
}
- private List<String> validateProperties(String content) {
- return SourceValidationSupport.validatePropertiesLines(content,
propertiesValidator::validate);
- }
-
- private List<Error> validateYaml(String content) {
- try {
- if (yamlValidator == null) {
- yamlValidator = new
org.apache.camel.dsl.yaml.validator.YamlValidator();
- }
- return yamlValidator.validate(content);
- } catch (Exception e) {
- return List.of();
- }
- }
-
private void notifySave(String message, boolean error) {
if (notificationCallback != null) {
notificationCallback.accept(message, error);
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
index 563f893b0ca8..20faa055005f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
@@ -31,6 +31,10 @@ explorer on the left and a source viewer on the right.
- The problems of a Camel file are marked as soon as it opens: a red ✗ on
their lines and the count in the title; the panel at the bottom says the
problem of the selected line, and **F9** goes to the next one
+- A note, something that works but is not right, is a yellow ⚠ instead, counted
+ apart as `notes`, and does not keep the file from being saved: a `direct:` or
+ `seda:` endpoint no route of the project consumes, a dependency a Kamelet
does
+ not use
- **u** — usages: the routes that consume from the endpoint of the line and the
steps that send to it (direct:, seda:...), across the project; **Enter**
goes there
- **/** — search in source
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
new file mode 100644
index 000000000000..f6cc0ba1c4e3
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.tui;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The checks of the source editor by file type: errors and notes, and which
errors keep a file from being saved.
+ */
+class SourceChecksTest {
+
+ private static final String YAML = """
+ - route:
+ from:
+ uri: timer:tick
+ steps:
+ - to: direct:cheese
+ """;
+
+ @Test
+ void anErrorAndANoteOnTheirLines() {
+ SourceChecks checks = new SourceChecks();
+ checks.setEndpointValidator(content -> List.of("Line 3: timer: unknown
option"));
+ checks.setNotes(content -> List.of("Line 5: no route consumes
direct:cheese", "Line 3: also a note"));
+ SourceChecks.Found found = checks.check(SourceChecks.Kind.YAML, YAML);
+ assertThat(found.errors()).containsExactly("Line 3: timer: unknown
option");
+ assertThat(found.notes()).hasSize(2);
+ // a line with an error and a note shows the error, and is not a note
line
+ assertThat(found.byLine(YAML)).containsEntry(2, "timer: unknown
option")
+ .containsEntry(4, "no route consumes direct:cheese");
+ assertThat(found.noteLines(YAML)).containsOnly(4);
+ }
+
+ @Test
+ void aSaveChecksTheSchemaAndLeavesOutTheNotesOfAFileItDoesNotSave() {
+ SourceChecks checks = new SourceChecks();
+ checks.setNotes(content -> List.of("Line 5: no route consumes
direct:cheese"));
+ String misspelled = YAML.replace("steps:", "stepz:");
+ // while typing: no schema check, and the note
+ assertThat(checks.check(SourceChecks.Kind.YAML,
misspelled).errors()).isEmpty();
+ assertThat(checks.check(SourceChecks.Kind.YAML,
misspelled).notes()).hasSize(1);
+ // on save: the schema error, which keeps the file from being saved,
and no note
+ SourceChecks.Found found = checks.checkOnSave(SourceChecks.Kind.YAML,
misspelled);
+ assertThat(found.errors()).isNotEmpty();
+ assertThat(found.notes()).isEmpty();
+ assertThat(found.blocksSave(SourceChecks.Kind.YAML)).isTrue();
+ // a valid file is saved with its note
+ SourceChecks.Found valid = checks.checkOnSave(SourceChecks.Kind.YAML,
YAML);
+ assertThat(valid.errors()).isEmpty();
+ assertThat(valid.notes()).hasSize(1);
+ assertThat(valid.blocksSave(SourceChecks.Kind.YAML)).isFalse();
+ }
+
+ @Test
+ void theErrorsOfAJavaFileDoNotBlockItsSave() {
+ SourceChecks checks = new SourceChecks();
+ checks.setRouteValidator(content -> List.of("Line 1: direct: unknown
option"));
+ SourceChecks.Found java = checks.checkOnSave(SourceChecks.Kind.OTHER,
"from(\"direct:a?foo=1\")");
+ assertThat(java.errors()).hasSize(1);
+ assertThat(java.blocksSave(SourceChecks.Kind.OTHER)).isFalse();
+ assertThat(checks.checkOnSave(SourceChecks.Kind.XML, "<from
uri=\"direct:a?foo=1\"/>")
+ .blocksSave(SourceChecks.Kind.XML)).isTrue();
+ // a Java file is saved with its errors, so its notes are said on save
too; an XML file's errors keep them out
+ checks.setNotes(content -> List.of("Line 1: no route consumes
direct:a"));
+ assertThat(checks.checkOnSave(SourceChecks.Kind.OTHER,
"x").notes()).hasSize(1);
+ assertThat(checks.checkOnSave(SourceChecks.Kind.XML,
"x").notes()).isEmpty();
+ assertThat(checks.check(SourceChecks.Kind.XML,
"x").notes()).hasSize(1);
+ // the checks of the file forgotten: nothing found
+ checks.clear();
+ assertThat(checks.check(SourceChecks.Kind.OTHER,
"x")).isSameAs(SourceChecks.Found.NONE);
+ }
+
+ @Test
+ void aPropertiesFileIsCheckedLineByLine() {
+ SourceChecks checks = new SourceChecks();
+ checks.setPropertiesValidator(line -> line.startsWith("camel.bad") ?
"unknown option" : null);
+ SourceChecks.Found found = checks.check(SourceChecks.Kind.PROPERTIES,
"camel.main.name=x\ncamel.bad=1\n");
+ assertThat(found.byLine("")).containsOnlyKeys(1);
+ assertThat(found.blocksSave(SourceChecks.Kind.PROPERTIES)).isTrue();
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
index ff9a16cda9cd..2fdcc5874d03 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceConsumerNotesTest.java
@@ -22,6 +22,7 @@ import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -37,9 +38,9 @@ import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
/**
- * A line of a YAML route that sends to a direct: or seda: endpoint no route
of the project consumes is marked as a note
- * in the Source tab (CAMEL-24955): a warning that does not block the save, as
the consuming route may not be written
- * yet. The other route files are read on a background thread, run here by
hand.
+ * A line of a YAML, Java or XML route that sends to a direct: or seda:
endpoint no route of the project consumes is
+ * marked as a note in the Source tab (CAMEL-24955): a warning that does not
block the save, as the consuming route may
+ * not be written yet. The other route files are read on a background thread,
run here by hand.
*/
class SourceConsumerNotesTest {
@@ -67,6 +68,7 @@ class SourceConsumerNotesTest {
Path tempDir;
private final Deque<Runnable> background = new ArrayDeque<>();
+ private final ConsumedEndpoints consumed = new
ConsumedEndpoints(background::add, Runnable::run);
private final AtomicReference<String> lastNotification = new
AtomicReference<>();
@BeforeEach
@@ -100,6 +102,30 @@ class SourceConsumerNotesTest {
.contains("sends to direct:cheese");
}
+ @Test
+ void theNoteGoesInTheEditorWhenAnotherFileConsumesTheEndpoint() throws
Exception {
+ Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER,
StandardCharsets.UTF_8);
+ Path file = tempDir.resolve("r.camel.yaml");
+ Files.writeString(file, ROUTE, StandardCharsets.UTF_8);
+ SourceViewer viewer = viewer(file);
+ viewer.loadFile(file);
+ runBackground();
+ viewer.enterEditMode();
+ assertThat(viewer.noteLines()).containsOnly(lineOf(ROUTE,
"direct:cheese"));
+ // another file consumes it now, and a file was opened: the next check
reads the files again
+ Files.writeString(tempDir.resolve("cheese.camel.yaml"),
OTHER.replace("direct:other", "direct:cheese"),
+ StandardCharsets.UTF_8);
+ consumed.expire();
+ viewer.recheck(file);
+ // until the read is done, what was read before
+ assertThat(viewer.noteLines()).isNotEmpty();
+ runBackground();
+ // the editor checks its text again, without a change of it
+ assertThat(viewer.inlineErrors()).isEmpty();
+ assertThat(viewer.noteLines()).isEmpty();
+ assertThat(viewer.isDirty()).isFalse();
+ }
+
@Test
void theNoteGoesWhenARouteConsumesTheEndpoint() throws Exception {
Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER,
StandardCharsets.UTF_8);
@@ -119,31 +145,78 @@ class SourceConsumerNotesTest {
assertThat(viewer.noteLines()).isEmpty();
}
+ @Test
+ void aJavaOrXmlRouteGetsTheNoteToo() throws Exception {
+ Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER,
StandardCharsets.UTF_8);
+ String java = """
+ import org.apache.camel.builder.RouteBuilder;
+
+ public class Tick extends RouteBuilder {
+ @Override
+ public void configure() {
+ from("timer:tick")
+ .to("direct:cheese")
+ .to("direct:other");
+ }
+ }
+ """;
+ Path file = tempDir.resolve("Tick.java");
+ Files.writeString(file, java, StandardCharsets.UTF_8);
+ SourceViewer viewer = viewer(file);
+ viewer.loadFile(file);
+ runBackground();
+ int cheese = lineOf(java, "direct:cheese");
+ assertThat(viewer.viewErrors()).containsOnlyKeys(cheese);
+ assertThat(viewer.viewErrors().get(cheese)).startsWith("sends to
direct:cheese, and no route consumes it");
+ assertThat(viewer.noteLines()).containsOnly(cheese);
+
+ String xml = """
+ <routes>
+ <route>
+ <from uri="timer:tick"/>
+ <to uri="direct:cheese"/>
+ </route>
+ </routes>
+ """;
+ Path xmlFile = tempDir.resolve("tick.camel.xml");
+ Files.writeString(xmlFile, xml, StandardCharsets.UTF_8);
+ SourceViewer xmlViewer = viewer(xmlFile);
+ xmlViewer.loadFile(xmlFile);
+ runBackground();
+ assertThat(xmlViewer.noteLines()).containsOnly(lineOf(xml,
"direct:cheese"));
+ // a note does not keep an XML file from being saved, as an error would
+ xmlViewer.enterEditMode();
+ appendSpaceToLine(xmlViewer, 1);
+ xmlViewer.handleKeyEvent(KeyEvent.ofChar('s', KeyModifiers.CTRL));
+ assertThat(Files.readString(xmlFile,
StandardCharsets.UTF_8)).contains("<route> ");
+ assertThat(lastNotification.get()).startsWith("Saved: tick.camel.xml
with 1 Camel problem: ");
+ }
+
@Test
void theOtherFilesAreReadOnceAndAgainForAnotherFile() throws Exception {
Files.writeString(tempDir.resolve("other.camel.yaml"), OTHER,
StandardCharsets.UTF_8);
ConsumedEndpoints consumed = new ConsumedEndpoints(background::add,
Runnable::run);
AtomicInteger changes = new AtomicInteger();
Path file = tempDir.resolve("r.camel.yaml");
- assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG,
changes::incrementAndGet)).isEmpty();
+ assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(),
changes::incrementAndGet)).isEmpty();
assertThat(background).hasSize(1);
// asked again while reading: no second read
- consumed.notes(file, tempDir, ROUTE, CATALOG,
changes::incrementAndGet);
+ consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(),
changes::incrementAndGet);
assertThat(background).hasSize(1);
runBackground();
assertThat(changes).hasValue(1);
- assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG,
changes::incrementAndGet))
+ assertThat(consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(),
changes::incrementAndGet))
.singleElement().asString().startsWith("Line 6: route tick:
sends to direct:cheese");
// read a moment ago: kept
assertThat(background).isEmpty();
// the other file opened before the read of the first one is done:
that read is followed by one for it
Path other = tempDir.resolve("other.camel.yaml");
- consumed.notes(file, tempDir, ROUTE, CATALOG,
changes::incrementAndGet);
+ consumed.notes(file, tempDir, ROUTE, CATALOG, Map.of(),
changes::incrementAndGet);
Files.writeString(tempDir.resolve("cheese.camel.yaml"),
OTHER.replace("direct:other", "direct:cheese"),
StandardCharsets.UTF_8);
consumed.get(tempDir, "nowhere.camel.yaml", CATALOG,
changes::incrementAndGet);
- assertThat(consumed.notes(other, tempDir, OTHER, CATALOG,
changes::incrementAndGet)).isEmpty();
+ assertThat(consumed.notes(other, tempDir, OTHER, CATALOG, Map.of(),
changes::incrementAndGet)).isEmpty();
runBackground();
assertThat(consumed.get(tempDir, "other.camel.yaml", CATALOG,
changes::incrementAndGet))
.containsOnly("direct:cheese");
@@ -158,19 +231,18 @@ class SourceConsumerNotesTest {
Files.writeString(elsewhere.resolve("other.camel.yaml"), OTHER,
StandardCharsets.UTF_8);
ConsumedEndpoints consumed = new ConsumedEndpoints(background::add,
Runnable::run);
Path file = elsewhere.resolve("r.camel.yaml");
- consumed.notes(file, project, ROUTE, CATALOG, () -> {
+ consumed.notes(file, project, ROUTE, CATALOG, Map.of(), () -> {
});
runBackground();
- assertThat(consumed.notes(file, project, ROUTE, CATALOG, () -> {
+ assertThat(consumed.notes(file, project, ROUTE, CATALOG, Map.of(), ()
-> {
})).singleElement().asString().contains("sends to direct:cheese");
}
private SourceViewer viewer(Path file) {
SourceViewer viewer = new SourceViewer();
- ConsumedEndpoints consumed = new ConsumedEndpoints(background::add,
Runnable::run);
viewer.setNotificationCallback((msg, error) ->
lastNotification.set(msg));
- viewer.setYamlNotes(content -> consumed.notes(file, tempDir, content,
CATALOG,
- () -> viewer.reloadIfShowing(file)));
+ viewer.setNotes(content -> consumed.notes(file, tempDir, content,
CATALOG, Map.of(),
+ () -> viewer.recheck(file)));
return viewer;
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
index d176687e28ed..1ebe851dc05e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceKameletValidationTest.java
@@ -265,7 +265,7 @@ class SourceKameletValidationTest {
SourceViewer viewer = new SourceViewer();
viewer.setNotificationCallback((msg, error) ->
lastNotification.set(msg));
viewer.setKameletValidator(content ->
SourceEditAssist.validateKamelets(file, content));
- viewer.setYamlNotes(content -> SourceEditAssist.kameletNotes(file,
content));
+ viewer.setNotes(content -> SourceEditAssist.kameletNotes(file,
content));
return viewer;
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
index c36832270de2..f6c8c6296167 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceSizeTest.java
@@ -76,7 +76,7 @@ class SourceSizeTest {
SendMessagePopup.handleKeyEvent 220
SourceEditAssist.java 2006
SourceViewer.handleEditKeyEvent 231
- SourceViewer.java 3055
+ SourceViewer.java 2953
SourceViewer.renderContent 207
SourceViewer.renderEditMode 256
TuiToolDefinitions.addInteractionTools 183