diff --git a/.github/ISSUE_TEMPLATE/version_update.md b/.github/ISSUE_TEMPLATE/version_update.md
index 5544634bc..efca3f72b 100644
--- a/.github/ISSUE_TEMPLATE/version_update.md
+++ b/.github/ISSUE_TEMPLATE/version_update.md
@@ -7,11 +7,11 @@ assignees: ''
## Description
-After the release of USE version ${{env.OLD_USE_VERSION}} it is tiome to change
+After the release of USE version ${{env.OLD_USE_VERSION}} it is time to change
the version to a new one. For this, please change the following files:
* ./pom.xml
* ./use-assembly/pom.xml
* ./use-core/pom.xml
* ./use-core/src/main/java/org/tzi/use/config/Options.java
-* ./use-gui/pom.xml
+* ./use-gui/pom.xml
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index b992b4245..882bb50c6 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,6 +1,9 @@
These tests represent nearly the exact behavior of the
+ * USE shell. Only some whitespace differences and time outputs are ignored. Each test consists of the following files:
+ *
-// *
-// *
-// * All .use) - this file provides the (possible empty) model used
-// * for the tests.in) - this file can contain any commands USE supports.
-// * The expected output must be specified by starting a line with a star *.use and .in files must share the same name, e.g., t555.use and t555.in for test
-// * case 555. These files must be placed in the folder it/resources/testfiles/shell.
-// *
-// * If an integration test fails, two additional files are created:
-// *
-// *
-// * These files can be used to easily diff expected and current output.
-// */
-//public class ShellIT {
-//
-// /**
-// * This TestFactory enumerates the {testcasename}.expected - The expected output calculated from the {testcase}.in-file{testcasename}.actual - The ouptut captured while running the test..in-files in th folder testfiles/shell.
-// * For each file a DynamicTest is created with the name of the file.
-// *
-// * @return A Stream with one DynamicTest for each *.in-file.
-// */
-// @TestFactory
-// public StreamFunction is used to map
-// * a given testinput-file given as a Path
-// * to a DynamicTest.
-// *
-// * @return A DynamicTest that uses the function assertShellExpression
-// * to test the given testinput file.
-// */
-// private Function
-// *
-// *
-// * @param testFile createCommandFile.useFile and the created command file (runUSE).validateOutput).Path to the test input file to execute.
-// * @param useFile Path to the USE file containing the model to load for the test.
-// */
-// private void assertShellExpressions(Path testFile, Path useFile) {
-//
-// Path cmdFile = testFile.resolveSibling(testFile.getFileName() + ".cmd");
-//
-// ListexpectedOutput
-// * and actualOutput.
-// * If they differ, two files are written at the location of the
-// * testFile. One with the expected output (.expected)
-// * and one with the actual output (.actual).
-// *
-// * @param testFile The Path to the testFile
-// * @param expectedOutput List of strings with the expected output (one String per line)
-// * @param actualOutput List of strings with the actual output (one String per line)
-// */
-// private void validateOutput(Path testFile, Listdata
-// * to the file loctaed by the Path file.
-// *
-// * If the file is not accessible, i.e., an IOException is thrown,
-// * the exceptions is cathed and the test case fails.
-// * @param data The List of string (lines) to write.
-// * @param file The path to the file to write (file is overwritten).
-// */
-// private void writeToFile(ListcmdFile.
-// * The file contains all commands that are specified in the inFile.
-// * The expected output, i.e., lines starting with a * are added to the list expectedOutput.
-// *
-// * @param inFile The Path to the test input file.
-// * @param cmdFile The Path where to create the command file.
-// * @return A List which is filled with the expected output of USE.
-// */
-// private ListuseFile as the model
-// * and the cmdFile to execute commands.
-// * The output is captured from the output and error streams.
-// *
-// * @param useFile Path to the USE model to load on startup
-// * @param cmdFile Path to the commands file to execute
-// *
-// * @return A List of strings. Each string is one line of output.
-// */
-// private List
+ *
+ *
All {@code .use and .in} files must share the same name, e.g., t555.use and t555.in for test
+ * case 555. These files must be placed in the folder {@code it/resources/testfiles/shell}.
If an integration test fails, two additional files are created: + *
{testcase}.in}-filetestfiles/shell}.
+ * For each file a {@code DynamicTest} is created with the name of the file.
+ *
+ * @return A {@code Stream with one DynamicTest for each *.in}-file.
+ */
+ @TestFactory
+ public Stream evaluateExpressionFiles() {
+ URL testDirURL = getClass().getClassLoader().getResource("testfiles/shell");
+ Path testDirPath = null;
+
+ if (testDirURL == null) {
+ fail("Directory for shell integration tests not found!");
+ }
+
+ try {
+ testDirPath = Path.of(testDirURL.toURI());
+ } catch (URISyntaxException e) {
+ fail("Directory for shell integration tests not found!");
+ }
+
+ try {
+ return Files.walk(testDirPath).filter(
+ path -> path.getFileName().toString().endsWith(".in")
+ ).map(mapInFileToTest());
+ } catch (IOException e) {
+ fail("Error iterating shell integration test input files!");
+ }
+
+ return Stream.empty();
+ }
+
+ /**
+ * This {@code Function} is used to map
+ * a given testinput-file given as a {@code Path}
+ * to a {@code DynamicTest}.
+ *
+ * @return A {@code DynamicTest that uses the function assertShellExpression}
+ * to test the given testinput file.
+ */
+ private Function mapInFileToTest() {
+ return path -> {
+ final String modelFilename = path.getFileName().toString().replace(".in", ".use");
+ final Path modelPath = path.resolveSibling(modelFilename);
+
+ return DynamicTest.dynamicTest(path.getFileName().toString(), path.toUri(), () -> assertShellExpressions(path, modelPath));
+ };
+ }
+
+ /**
+ * This function controls the overall process for test for a single testfile.
+ *
+ * The process is as follows:
+ *
+ * - A command file and the expected output are created by examining the input file (via {@code createCommandFile}).
+ * - USE is executed using the {@code useFile
and the created command file (runUSE}).
+ * The output of USE is compared to the expected output created in 1. ({@code validateOutput}).
+ *
+ *
+ *
+ * @param testFile {@code Path} to the test input file to execute.
+ * @param useFile {@code Path} to the USE file containing the model to load for the test.
+ */
+ private void assertShellExpressions(Path testFile, Path useFile) {
+
+ Path cmdFile = testFile.resolveSibling(testFile.getFileName() + ".cmd");
+
+ List expectedOutput = createCommandFile(testFile, cmdFile);
+
+ List actualOutput = runUSE(useFile, cmdFile).collect(Collectors.toList());
+
+ validateOutput(testFile, expectedOutput, actualOutput);
+ }
+
+ /**
+ * Compares the two lists of strings {@code expectedOutput}
+ * and {@code actualOutput}.
+ * If they differ, two files are written at the location of the
+ * {@code testFile . One with the expected output (.expected})
+ * and one with the actual output ({@code .actual}).
+ *
+ * @param testFile The {@code Path to the testFile}
+ * @param expectedOutput List of strings with the expected output (one String per line)
+ * @param actualOutput List of strings with the actual output (one String per line)
+ */
+ private void validateOutput(Path testFile, List expectedOutput, List actualOutput) {
+ //create a configured DiffRowGenerator
+ DiffRowGenerator generator = DiffRowGenerator.create()
+ .showInlineDiffs(true)
+ .mergeOriginalRevised(true)
+ .inlineDiffByWord(true)
+ .ignoreWhiteSpaces(true)
+ .lineNormalizer( (s) -> s ) // No normalization required
+ .oldTag((f, start) -> start ? "-\033[9m" : "\033[m-") //introduce style for strikethrough
+ .newTag((f, start) -> start ? "+\033[97;42m" : "\033[m+") //introduce style for bold
+ .build();
+
+ //compute the differences for two test texts.
+ List rows = generator.generateDiffRows(expectedOutput, actualOutput);
+ Predicate filter = d -> d.getTag() != DiffRow.Tag.EQUAL;
+
+ if (rows.stream().anyMatch(filter)) {
+ StringBuilder diffMsg = new StringBuilder("USE output does not match expected output!").append(System.lineSeparator());
+
+ diffMsg.append("Test file: ").append(testFile).append(System.lineSeparator());
+
+ diffMsg.append(System.lineSeparator()).append("Note: the position is not the position in the input file!");
+ diffMsg.append(System.lineSeparator()).append(System.lineSeparator());
+
+ rows.stream().filter(filter).forEach(
+ row ->diffMsg.append(System.lineSeparator()).append(row.getOldLine())
+ );
+
+ writeToFile(expectedOutput, testFile.getParent().resolve(testFile.getFileName().toString() + ".expected"));
+ writeToFile(actualOutput, testFile.getParent().resolve(testFile.getFileName().toString() + ".actual"));
+
+ fail(diffMsg.toString());
+ }
+ }
+
+ /**
+ * Helper method that writes the list of strings {@code data}
+ * to the file located by the {@code Path} {@code file}.
+ *
+ * If the file is not accessible, i.e., an IOException is thrown,
+ * the exception is caught and the test case fails.
+ * @param data The {@code List} of string (lines) to write.
+ * @param file The path to the file to write (the file is overwritten).
+ */
+ private void writeToFile(List data, Path file) {
+ try (FileWriter writer = new FileWriter(file.toFile())) {
+
+ for (String line : data) {
+ writer.write(line);
+ writer.write(System.lineSeparator());
+ }
+ } catch (IOException e) {
+ fail("Test output could not be written!", e);
+ }
+ }
+
+ /**
+ * Creates a USE-command file at the position located by the path {@code cmdFile}.
+ * The file contains all commands that are specified in the {@code inFile}.
+ * The expected output, i.e., lines starting with a {@code *} are added to the list {@code expectedOutput}.
+ *
+ * @param inFile The {@code Path} to the test input file.
+ * @param cmdFile The {@code Path} where to create the command file.
+ * @return A {@code List} which is filled with the expected output of USE.
+ */
+ private List createCommandFile(Path inFile, Path cmdFile) {
+ List expectedOutput = new LinkedList<>();
+
+ // Build USE command file and build expected output
+ try (
+ Stream linesStream = Files.lines(inFile, StandardCharsets.UTF_8);
+ FileWriter cmdWriter = new FileWriter(cmdFile.toFile(), StandardCharsets.UTF_8, false)
+ ) {
+
+ linesStream.forEach(inputLine -> {
+
+ // Ignore empty lines in expected, since they are also suppressed in the actual output
+ if (inputLine.isBlank())
+ return;
+
+ if ((inputLine.startsWith("*") || inputLine.startsWith("#"))
+ && inputLine.substring(1).isBlank()) {
+ return;
+ }
+
+ if (inputLine.startsWith("*")) {
+ // Input line minus prefix(*) is expected output
+ expectedOutput.add(inputLine.substring(1).trim());
+ } else if (!inputLine.startsWith("#")) { // Not a comment
+ try {
+ cmdWriter.write(inputLine);
+ cmdWriter.write(System.lineSeparator());
+
+ // Multi-line commands (backslash and dot) are ignored
+ if (!inputLine.matches("^[\\\\.]$")) {
+ expectedOutput.add(inputLine);
+ }
+ } catch (IOException e1) {
+ fail("Could not write USE command file for test!", e1);
+ }
+ }
+ });
+ } catch (IOException e) {
+ fail("Could not write USE command file for test!", e);
+ }
+
+ return expectedOutput;
+ }
+
+ /**
+ * Executes USE with the given {@code useFile} as the model
+ * and the {@code cmdFile} to execute commands.
+ * The output is captured from the output and error streams.
+ *
+ * @param useFile Path to the USE model to load on startup
+ * @param cmdFile Path to the commands file to execute
+ * @return A {@code List} of strings. Each string is one line of output.
+ */
+ private Stream runUSE(Path useFile, Path cmdFile) {
+
+ // We need to specify a concrete locale to always get the same formatted result
+ Locale.setDefault(Locale.forLanguageTag("en-US"));
+
+ Options.resetOptions();
+ USEWriter.getInstance().clearLog();
+
+ String homeDir;
+ try {
+ homeDir = useFile.getParent().resolve("../../../../../use-core/target/classes").toFile().getCanonicalPath();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ String[] args = new String[] {
+ "-nogui",
+ "-nr",
+ "-t",
+ "-it",
+ "-q",
+ "-oclAnyCollectionsChecks:E",
+ "-extendedTypeSystemChecks:E",
+ /* This is currently an unstable workaround
+ USE determines the plugin and the extensions to OCL by fixed paths.
+ For now, the use-core module contains the directories including the extensions
+ and an empty plugins folder.
+ The folder is located: use/use-core/target/classes
+ Therefore, this is used as the USE home
+ */
+ "-H=" + homeDir,
+ useFile.toString(),
+ cmdFile.toString()};
+
+ Main.main(args);
+
+ try (ByteArrayOutputStream protocol = new ByteArrayOutputStream()) {
+ USEWriter.getInstance().writeProtocolFile(protocol);
+ String output = protocol.toString();
+ return output.lines().filter(l -> !l.isBlank());
+ } catch (IOException e) {
+ fail(e);
+ }
+
+ return Stream.empty();
+ }
+}
diff --git a/use-gui/src/main/java/module-info.java b/use-gui/src/main/java/module-info.java
index 0846bb894..28a36d0fd 100644
--- a/use-gui/src/main/java/module-info.java
+++ b/use-gui/src/main/java/module-info.java
@@ -1,15 +1,14 @@
module use.gui {
- requires org.eclipse.jdt.annotation;
requires use.core;
requires vtd.xml;
- requires com.google.common;
requires itextpdf;
requires javafx.fxml;
requires javafx.web;
requires org.kordamp.desktoppanefx.core;
requires javafx.swing;
requires annotations;
- requires jline;
+ requires lombok;
+ requires com.google.common;
opens org.tzi.use.gui.main to javafx.fxml;
exports org.tzi.use.main.gui;
exports org.tzi.use.gui.views;
@@ -32,6 +31,4 @@
exports org.tzi.use.gui.views.diagrams.behavior.shared to com.google.common;
exports org.tzi.use.gui.views.selection to com.google.common;
exports org.tzi.use.gui.views.diagrams.statemachine to com.google.common;
-
-
}
\ No newline at end of file
diff --git a/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java b/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java
index ed54cfc10..157bd4aee 100644
--- a/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java
+++ b/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java
@@ -1418,7 +1418,7 @@ private void createClassDiagram() {
// Calling the Swing MainWindow to get the ClassDiagram out of it
org.tzi.use.gui.main.MainWindow mainwindow = org.tzi.use.gui.main.MainWindow.create(fSession, fPluginRuntime);
- ClassDiagramView cdv = new ClassDiagramView(mainwindow, fSession.system(), loadLayout);
+ ClassDiagramView cdv = new ClassDiagramView(mainwindow, fSession.system(), loadLayout, fPluginRuntime);
ViewFrame f = new ViewFrame("Class diagram", cdv, "ClassDiagram.gif");
JComponent c = (JComponent) f.getContentPane();
diff --git a/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java b/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java
index 983eaaab0..2cd4efd9b 100644
--- a/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java
+++ b/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java
@@ -30,7 +30,6 @@
import java.util.Map;
import java.util.Set;
-import lombok.Getter;
import org.tzi.use.graph.DirectedEdgeBase;
import org.tzi.use.gui.util.PersistHelper;
import org.tzi.use.gui.views.diagrams.DiagramOptions;
@@ -155,7 +154,7 @@ public enum PropertyOwner {
* @param source The source node of the edge.
* @param target The target node of the edge.
* @param edgeName The name of the edge.
- * @param diagram The diagram this edge belongs to.
+ * @param opt The diagram options this edge uses to.
* @param completeEdgeMoveMovesUserWayPoints If true, user defined way points are moved by the edge if
* source and target are selected.
*/
diff --git a/use-gui/src/main/java/org/tzi/use/main/gui/Main.java b/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
index 6ac6ec488..258cbaea0 100644
--- a/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
+++ b/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
@@ -2,15 +2,21 @@
import org.tzi.use.main.gui.fx.MainJavaFX;
import org.tzi.use.main.gui.swing.MainSwing;
+import org.tzi.use.util.USEWriter;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
+ // set System.out to the USEWriter to protocol the output.
+ System.setOut(USEWriter.getInstance().getOut());
+ // set System.err to the USEWriter to protocol the output.
+ System.setErr(USEWriter.getInstance().getErr());
+
boolean useJavaFX = false;
// cleanedArgs is needed to avoid giving -jfx into the MainJavaFX,
- // because Application.launch() tries to parse all arguments as JavaFX-compatible
+ // because Application.launch() tries to parse all arguments as JavaFX compatible
// and throws an error if it encounters an unknown one (like -jfx).
List cleanedArgs = new ArrayList<>();
for (String arg : args) {