diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 7af4cf60d..4d66ebab5 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -139,6 +139,7 @@ Add a few simple unit tests to help maintain this enumeration. Add possibility for validation rules to specify their severity ; the SysML rules from the specification remain as WARNINGs for now. Tweak the diagnostic messages to help users understand where the erroneous element is in their editing context. Make sure diagnostics are sorted according to their validated element and its containing document. +- https://github.com/eclipse-syson/syson/issues/2042[#2042] [import] Improve error reporting while uploading a document. === New features diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java index 59a018b8b..76118e360 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java @@ -318,6 +318,22 @@ public void testReferentialUsages() throws IOException { }); } + @Test + @DisplayName("GIVEN a really big SysML model, WHEN importing that model, THEN the import fails but a error message is returned") + public void checkBigSysmlFileFailure() throws IOException { + + final StringBuilder builder = this.generateInDepthSysmlStructure(3000, 20); + + String input = builder.toString(); + this.checker.addExpectedReportMessage("[ERROR] Error: File size exceeds limit : The selected SysML file is too large to be processed by SysON." + + " Please optimize the model or split it into smaller sub-packages and try again.") + .checkImportedModel(resource -> { + assertThat(resource.getContents()).isEmpty(); + }) + .check(input); + } + + @Test @DisplayName("GIVEN of model with redefinition depending on inherited memberships computation, WHEN importing the model, THEN redefined feature should resolve properly using inherited " + "memberships") @@ -1331,4 +1347,22 @@ private void assertLiteralStringValue(String expected, Feature f) { .extracting(e -> ((LiteralString) e).getValue()) .isEqualTo(expected); } + + private void generateInDepthSysmlStructure(StringBuilder builder, int prefix, int level, int maxDepth) { + builder.append("\t".repeat(level)).append("package pack_").append(prefix).append("_").append(level).append("{").append(System.lineSeparator()); + builder.append("\t".repeat(level)).append(" part part_").append(prefix).append("_").append(level).append(";").append(System.lineSeparator()); + + if (level < maxDepth) { + this.generateInDepthSysmlStructure(builder, prefix, level + 1, maxDepth); + } + builder.append("\t".repeat(level)).append("}").append(System.lineSeparator()); + } + + private StringBuilder generateInDepthSysmlStructure(int width, int depth) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < width; i++) { + this.generateInDepthSysmlStructure(builder, i, 0, depth); + } + return builder; + } } diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/SysMLv2SemanticImportChecker.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/SysMLv2SemanticImportChecker.java index 9f5373336..7c9ce8f0b 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/SysMLv2SemanticImportChecker.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/SysMLv2SemanticImportChecker.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.syson.application.imports; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; @@ -25,6 +26,7 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.sirius.web.application.document.services.LoadingReport; import org.eclipse.sirius.web.application.document.services.api.ExternalResourceLoadingResult; import org.eclipse.sirius.web.application.editingcontext.EditingContext; import org.eclipse.syson.sysml.upload.SysMLExternalResourceLoaderService; @@ -40,6 +42,8 @@ public class SysMLv2SemanticImportChecker { private final List> semanticCheckers = new ArrayList<>(); + private final List expectedErrorMessages = new ArrayList<>(); + private final EditingContext editingContext; @@ -58,12 +62,22 @@ public void check(String importedText) throws IOException { assertTrue(optExternalResourceLoadingResult.isPresent()); ExternalResourceLoadingResult externalResourceLoadingResult = optExternalResourceLoadingResult.get(); + if (!this.expectedErrorMessages.isEmpty()) { + LoadingReport report = (LoadingReport) externalResourceLoadingResult.loadingReport(); + assertThat(report.content()).isEqualTo(this.expectedErrorMessages); + } + for (Consumer checker : this.semanticCheckers) { checker.accept(externalResourceLoadingResult.resource()); } } } + public SysMLv2SemanticImportChecker addExpectedReportMessage(String report) { + this.expectedErrorMessages.add(report); + return this; + } + public SysMLv2SemanticImportChecker checkImportedModel(Consumer checker) { this.semanticCheckers.add(checker); return this; diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/data/generator/SysMLFileToXMIModel.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/data/generator/SysMLFileToXMIModel.java index 9b7ac0e34..cdb314996 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/data/generator/SysMLFileToXMIModel.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/data/generator/SysMLFileToXMIModel.java @@ -28,7 +28,9 @@ import org.eclipse.emf.ecore.xmi.XMIResource; import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl; import org.eclipse.syson.sysml.ASTTransformer; +import org.eclipse.syson.sysml.AstParsingResult; import org.eclipse.syson.sysml.SysmlToAst; +import org.eclipse.syson.sysml.textual.utils.Status; /** * Converts a SyML file to its XMI representation using SysON import. @@ -89,23 +91,33 @@ private void convert(String targetFilePath, File sysmlFile, ResourceSet resource SysmlToAst sysmlToAst = new SysmlToAst(null); ASTTransformer astTransformer = new ASTTransformer(); try (InputStream inputStream = new FileInputStream(sysmlFile)) { - InputStream astStream = sysmlToAst.convert(inputStream, "sysml"); - Resource resource = astTransformer.convertResource(astStream, resourceSet); - - if (resource != null && !resource.getContents().isEmpty()) { - System.out.println("Model parsed successfully."); - XMIResource resourceToSave = new XMIResourceImpl(URI.createFileURI(targetFilePath)); - resourceToSave.getContents().addAll(resource.getContents()); - resourceToSave.save(Collections.emptyMap()); - if (!astTransformer.getTransformationMessages().isEmpty()) { - final String errorMessage = astTransformer.getTransformationMessages().stream() - .map(message -> message.level().toString() + " - " + message.body()) - .collect(Collectors.joining("\n", "\n", "\n")); - System.err.println("Error while parsing input file : " + errorMessage); + AstParsingResult astResult = sysmlToAst.convert(inputStream, "sysml"); + + if (!astResult.reports().isEmpty()) { + final String errorMessage = astResult.reports().stream() + .map(Status::toString) + .collect(Collectors.joining(System.lineSeparator(), System.lineSeparator(), System.lineSeparator())); + System.err.println("[AST] while parsing input file : " + errorMessage); + } + + if (astResult.ast().isPresent()) { + Resource resource = astTransformer.convertResource(astResult.ast().get(), resourceSet); + + if (resource != null && !resource.getContents().isEmpty()) { + System.out.println("Model parsed successfully."); + XMIResource resourceToSave = new XMIResourceImpl(URI.createFileURI(targetFilePath)); + resourceToSave.getContents().addAll(resource.getContents()); + resourceToSave.save(Collections.emptyMap()); + } else { + System.err.println("Failed to parse resource or resource is empty."); } + } - } else { - System.err.println("Failed to parse resource or resource is empty."); + if (!astTransformer.getTransformationMessages().isEmpty()) { + final String errorMessage = astTransformer.getTransformationMessages().stream() + .map(message -> message.level().toString() + " - " + message.body()) + .collect(Collectors.joining(System.lineSeparator(), System.lineSeparator(), System.lineSeparator())); + System.err.println("Error while parsing input file : " + errorMessage); } } } diff --git a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/AstParsingResult.java b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/AstParsingResult.java new file mode 100644 index 000000000..7a3c4fe3b --- /dev/null +++ b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/AstParsingResult.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2026 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.syson.sysml; + +import java.io.InputStream; +import java.util.List; +import java.util.Optional; + +import org.eclipse.syson.sysml.textual.utils.Status; + +/** + * Result of the parsing of the AST. + * + * @param ast an optional {@link InputStream} of the AST + * @param reports parsing messages to be reported to the user + * + * @author Arthur Daussy + */ +public record AstParsingResult(Optional ast, List reports) { +} diff --git a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/SysmlToAst.java b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/SysmlToAst.java index 66d955d76..bff7ee46e 100644 --- a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/SysmlToAst.java +++ b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/SysmlToAst.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -14,16 +14,23 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import org.eclipse.syson.sysml.textual.utils.Severity; +import org.eclipse.syson.sysml.textual.utils.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -48,13 +55,16 @@ public SysmlToAst(@Value("${org.eclipse.syson.syside.path:#{null}}") final Strin this.cliPath = cliPath; } - public InputStream convert(final InputStream input, final String fileExtension) { - InputStream output = null; + public AstParsingResult convert(final InputStream input, final String fileExtension) { + Path sysmlInputPath = null; + Path sysIdeInputPath = null; + Optional astInputStream = Optional.empty(); + List reports = new ArrayList<>(); try { - final Path sysmlInputPath = this.createTempFile(input, "syson", fileExtension); + sysmlInputPath = this.createTempFile(input, "syson", fileExtension); + - Path sysIdeInputPath = null; if (this.cliPath != null) { sysIdeInputPath = Path.of(this.cliPath); } else { @@ -64,47 +74,115 @@ public InputStream convert(final InputStream input, final String fileExtension) sysIdeInputPath = this.createTempFile(sysIdeInputStream, "syside-cli", "js"); } - this.logger.info("Call syside application : node " + sysIdeInputPath.toString() + " dump " + sysmlInputPath.toString()); + this.logger.info("Call syside application : node " + sysIdeInputPath + " dump " + sysmlInputPath); final String[] args = { "node", sysIdeInputPath.toString(), "dump", sysmlInputPath.toString() }; ProcessBuilder pb = new ProcessBuilder(args); - pb = pb.redirectErrorStream(false); final Process sysIdeProcess = pb.start(); - final InputStream is = sysIdeProcess.getInputStream(); - final InputStreamReader isr = new InputStreamReader(is); - final BufferedReader br = new BufferedReader(isr); - final StringBuilder builder = new StringBuilder(); - String line = br.readLine(); - if (line != null) { - while (!line.contains("{")) { - line = br.readLine(); - } - builder.append(line); - while ((line = br.readLine()) != null) { - builder.append(line); + + CompletableFuture stdoutFuture = this.readStdOut(sysIdeProcess, reports); + CompletableFuture stderrFuture = this.readStdErr(sysIdeProcess, reports); + + this.handleStdError(stderrFuture.join(), reports); + boolean finished = sysIdeProcess.waitFor(60, TimeUnit.SECONDS); + + if (finished) { + String stdout = stdoutFuture.join(); + int exitCode = sysIdeProcess.exitValue(); + if (exitCode == 0) { + astInputStream = Optional.of(new ByteArrayInputStream(stdout.getBytes())); + } else { + this.logger.error("The process that parse the SysML file ended with an error core : {}", exitCode); } } else { - final InputStream er = sysIdeProcess.getErrorStream(); - final InputStreamReader err = new InputStreamReader(er); - final BufferedReader erbr = new BufferedReader(err); - this.logger.error("Fail to call syside application : \n " + erbr.lines().collect(Collectors.joining("\n"))); + reports.add(new Status(Severity.ERROR, "Process timed out : The upload process was canceled.")); + sysIdeProcess.destroyForcibly(); } - output = new ByteArrayInputStream(builder.toString().getBytes()); - - sysmlInputPath.toFile().delete(); + } catch (final IOException | InterruptedException e) { + this.logger.error(e.getMessage()); + } finally { + if (sysmlInputPath != null) { + sysmlInputPath.toFile().delete(); + } if (this.cliPath == null) { sysIdeInputPath.toFile().delete(); } + } + return new AstParsingResult(astInputStream, reports); - } catch (final IOException e) { - this.logger.error(e.getMessage()); + + } + + private CompletableFuture readStdErr(Process sysIdeProcess, List messages) { + return CompletableFuture.supplyAsync( + () -> { + try { + return this.readStream(sysIdeProcess.getErrorStream()); + } catch (IOException e) { + this.logger.error("Error while reading AST : " + e.getMessage(), e); + messages.add(new Status(Severity.ERROR, "Error while building AST on stdErr.")); + return ""; + } + }); + } + + private CompletableFuture readStdOut(Process sysIdeProcess, List messages) { + return CompletableFuture.supplyAsync( + () -> { + try { + return this.readAstStream(sysIdeProcess.getInputStream()); + } catch (IOException e) { + this.logger.error("Error while reading AST : " + e.getMessage(), e); + messages.add(new Status(Severity.ERROR, "Error while building AST on stdOut.")); + return ""; + } + }); + } + + private void handleStdError(String stderr, List reports) { + if (stderr != null && !stderr.isBlank()) { + this.logger.error("AST parsing errors :" + System.lineSeparator() + stderr); + + if (stderr.contains("JSON.stringify")) { + // This case occurs when the provided JSon file is too big + reports.add(new Status(Severity.ERROR, "Error: File size exceeds limit : The selected SysML file is too large to be processed by SysON." + + " Please optimize the model or split it into smaller sub-packages and try again.")); + } else { + reports.add(new Status(Severity.ERROR, "An unhandled exception has occurred during file parsing. Contact your administrator.")); + } + } + } + + private String readStream(InputStream stream) throws IOException { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + return reader.lines() + .collect(Collectors.joining(System.lineSeparator())); } + } - return output; + private String readAstStream(InputStream stream) throws IOException { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + StringBuilder builder = new StringBuilder(); + String line = reader.readLine(); + if (line != null) { + while (line != null && !line.contains("{")) { + line = reader.readLine(); + } + if (line != null) { + builder.append(line); + while ((line = reader.readLine()) != null) { + builder.append(line); + } + } + } + return builder.toString(); + } } - private Path createTempFile(final InputStream input, final String fileName, final String fileExtension) throws IOException, FileNotFoundException { + private Path createTempFile(final InputStream input, final String fileName, final String fileExtension) throws IOException { final Path inputPath = Files.createTempFile(fileName, "." + fileExtension); final OutputStream outStream = new FileOutputStream(inputPath.toString()); final byte[] buffer = new byte[8 * 1024]; diff --git a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/dto/InsertTextualSysMLv2EventHandler.java b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/dto/InsertTextualSysMLv2EventHandler.java index 55e215ed9..30b2ad919 100644 --- a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/dto/InsertTextualSysMLv2EventHandler.java +++ b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/dto/InsertTextualSysMLv2EventHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -13,6 +13,7 @@ package org.eclipse.syson.sysml.dto; import java.io.ByteArrayInputStream; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -33,6 +34,7 @@ import org.eclipse.syson.sysml.ASTTransformer; import org.eclipse.syson.sysml.Element; import org.eclipse.syson.sysml.SysmlToAst; +import org.eclipse.syson.sysml.textual.utils.Status; import org.springframework.stereotype.Service; import io.micrometer.core.instrument.Counter; @@ -82,21 +84,18 @@ public void handle(One payloadSink, Many changeDesc IPayload payload = null; if (input instanceof InsertTextualSysMLv2Input insertTextualInput && editingContext instanceof IEMFEditingContext emfEditingContext) { + messages = new ArrayList<>(); var parentObjectId = insertTextualInput.objectId(); var parentElement = this.getParentElement(parentObjectId, emfEditingContext); if (parentElement != null) { - var tranformer = new ASTTransformer(); - var newObjects = this.convert(insertTextualInput, emfEditingContext, tranformer, parentElement); + var transformer = new ASTTransformer(); + var newObjects = this.convert(insertTextualInput, emfEditingContext, transformer, parentElement, messages); + messages.addAll(transformer.getTransformationMessages()); if (!newObjects.isEmpty()) { - messages = tranformer.getTransformationMessages(); payload = new SuccessPayload(input.id(), messages); changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, editingContext.getId(), input); } else { - if (!tranformer.getTransformationMessages().isEmpty()) { - messages = tranformer.getTransformationMessages(); - } else { - messages = List.of(new Message("Unable to convert the input into valid SysMLv2", MessageLevel.ERROR)); - } + messages.add(new Message("Unable to convert the input into valid SysMLv2", MessageLevel.ERROR)); } } } @@ -120,12 +119,33 @@ private Element getParentElement(String parentObjectId, IEMFEditingContext emfEd return null; } - private List convert(InsertTextualSysMLv2Input insertTextualInput, IEMFEditingContext emfEditingContext, ASTTransformer tranformer, Element parentElement) { + private List convert(InsertTextualSysMLv2Input insertTextualInput, IEMFEditingContext emfEditingContext, ASTTransformer transformer, Element parentElement, List messages) { var textualContent = insertTextualInput.textualContent(); var resourceSet = emfEditingContext.getDomain().getResourceSet(); var inputStream = new ByteArrayInputStream(textualContent.getBytes()); - var astStream = this.sysmlToAst.convert(inputStream, ".sysml"); - var newObjects = tranformer.convertToElements(astStream, resourceSet, parentElement); - return newObjects; + var astParsingResult = this.sysmlToAst.convert(inputStream, ".sysml"); + messages.addAll(astParsingResult.reports().stream() + .map(this::toMessage) + .filter(Objects::nonNull) + .toList()); + if (astParsingResult.ast().isPresent()) { + return transformer.convertToElements(astParsingResult.ast().get(), resourceSet, parentElement); + } else { + return List.of(); + } + } + + private Message toMessage(Status status) { + MessageLevel msgLevel = switch (status.severity()) { + case INFO -> MessageLevel.INFO; + case WARNING -> MessageLevel.WARNING; + case ERROR -> MessageLevel.ERROR; + default -> null; + }; + if (msgLevel != null) { + return new Message(status.message(), msgLevel); + } else { + return null; + } } } diff --git a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/upload/SysMLExternalResourceLoaderService.java b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/upload/SysMLExternalResourceLoaderService.java index 621b19cd6..13437bedb 100644 --- a/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/upload/SysMLExternalResourceLoaderService.java +++ b/backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/upload/SysMLExternalResourceLoaderService.java @@ -18,17 +18,22 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.Optional; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.sirius.components.emf.services.JSONResourceFactory; import org.eclipse.sirius.web.application.document.services.LoadingReport; import org.eclipse.sirius.web.application.document.services.api.ExternalResourceLoadingResult; import org.eclipse.sirius.web.application.document.services.api.IExternalResourceLoaderService; import org.eclipse.syson.sysml.ASTTransformer; +import org.eclipse.syson.sysml.AstParsingResult; import org.eclipse.syson.sysml.SysmlToAst; +import org.eclipse.syson.sysml.textual.utils.Status; import org.eclipse.syson.sysml.util.ElementUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,15 +78,25 @@ public boolean canHandle(InputStream inputStream, URI resourceURI, ResourceSet r @Override public Optional getResource(InputStream inputStream, URI resourceURI, ResourceSet resourceSet, boolean applyMigrationParticipants) { - InputStream astStream = this.sysmlToAst.convert(inputStream, resourceURI.fileExtension()); - ASTTransformer transformer = new ASTTransformer(); - Resource resource = transformer.convertResource(astStream, resourceSet); - if (resource != null) { - ElementUtil.setIsImported(resource, true); - resourceSet.getResources().add(resource); + AstParsingResult astResult = this.sysmlToAst.convert(inputStream, resourceURI.fileExtension()); + + List reports = new ArrayList<>(astResult.reports().stream().map(Status::toString).toList()); + final Resource resource; + if (astResult.ast().isPresent()) { + ASTTransformer transformer = new ASTTransformer(); + resource = transformer.convertResource(astResult.ast().get(), resourceSet); + if (resource != null) { + ElementUtil.setIsImported(resource, true); + resourceSet.getResources().add(resource); + } + reports.addAll(transformer.logTransformationMessages()); + + } else { + // It seems there is a problem in the API, to be able to report an error, we need a resource. + // https://github.com/eclipse-sirius/sirius-web/issues/6281 + resource = new JSONResourceFactory().createResourceFromPath(null); } - var loadingReport = new LoadingReport(transformer.logTransformationMessages()); - var result = new ExternalResourceLoadingResult(resource, loadingReport); - return Optional.ofNullable(result); + return Optional.of(new ExternalResourceLoadingResult(resource, new LoadingReport(reports))); + } } diff --git a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/utils/Status.java b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/utils/Status.java index 1eb73b9e2..f08b36cb2 100644 --- a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/utils/Status.java +++ b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/textual/utils/Status.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024, 2025 Obeo. + * Copyright (c) 2024, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -48,4 +48,9 @@ public void log(Logger logger) { default -> logger.error(this.message); } } + + @Override + public String toString() { + return "[" + this.severity + "] " + this.message; + } } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc index 08914e3fe..b43cd250c 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc @@ -13,7 +13,7 @@ This feature is currently considered experimental. Try it out and give feedback by reporting bugs and suggesting new features. It's not recommended for production use. -* Downtream applications can now easily choose which SysML validation rules to apply, by providing an implementation of `ISysMLValidationRulesProvider`. +* Downstream applications can now easily choose which SysML validation rules to apply, by providing an implementation of `ISysMLValidationRulesProvider`. They can also add their own SysML-based validation rules by returning additional `ISysMLValidationRule` instances. @@ -158,6 +158,14 @@ package root { } ``` ** Implement the textual export for `AllocationUsage` and `AllocationDefinition`. +** A limitation has been identified regarding the size of the textual SysML model that can be imported. +An explicit error message has been added to the upload report to help identify this issue. ++ +[WARNING] +==== +Due to an API limitation, the upload may still appear to be successful, but the created document is empty. +An error message is available in the upload report. +==== * In _Details_ view: