diff --git a/.github/workflows/Publish.yml b/.github/workflows/Publish.yml
index 79f693e..c80d09d 100644
--- a/.github/workflows/Publish.yml
+++ b/.github/workflows/Publish.yml
@@ -86,4 +86,28 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload ${{ github.event.release.tag_name }} $(ls reta-*.msi reta*.deb reta*.rpm reta-*.zip 2>/dev/null)
-
\ No newline at end of file
+
+ publish-page:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Setup JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: 21
+ - name: Build Site
+ run: ./mvnw -B clean site site:stage
+ - name: Publish GitHub Pages
+ run: |
+ git config user.name ben12
+ git config user.email ben12@users.noreply.github.com
+ git clone --depth 1 --branch gh-pages https://github.com/ben12/reta.git gh-pages
+ cd gh-pages
+ rm -rf *
+ cp -r ../target/staging/* ./
+ git add --all
+ git commit -m "Update GitHub Pages"
+ git push
+
diff --git a/README.md b/README.md
index 371f718..82ae7de 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,53 @@
-reta
-====
-
-Requirement Engineering Traceability Analysis
+Requirement Engineering Traceability Analysis (RETA)
+===
http://reta.ben12.eu
+RETA (Requirement Engineering Traceability Analysis) can read any source of documents or code, extracts the requirements and requirement references, and analyses them for generate the traceability matrix.
+
+# Installation
+
+Download then install [the last release](https://github.com/ben12/reta/releases/latest).
+
+# Plugins
+
+## TIKA plugin
+
+TIKA plugin uses [Apache Tika](https://tika.apache.org/) to read any document (doc, xls, pdf, ...) and extracts requirements and requirement references from it.
+
+### Configuration
+
+Requirements and references are extracted from documents using regular expression. You can find documentation on the web and for example here : [Pattern JAVADOC](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/regex/Pattern.html#construct)
+
+So, if your requirements are formatted like this in your document :
+
+> REQ_RETA_SRS_*NNNN*_*v* - *summary*
+>
+> *content*
+>
+> REQ_END
+
+Where "NNNN" is the requirement number, "v" the version, "summary" the summary description and "content" the detailed description.
+
+Then, regular expression to match requirement start may be :
+ `^[ \t]*REQ_((RETA_SRS_\d+)_(\w+)[\s-]+(.*?))$`
+And regular expression to match requirement end may be :
+ `^[ \t]*REQ_END[ \t]*$`
+
+Bracket will capture part of text which will be used, for example, for identify the requirement reference using "Id" attribute ([Pattern JAVADOC](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/regex/Pattern.html#cg)).
+For done this, you must identify each capture group using "Index of regular expression groups in requirement starts" table and "Index of regular expression groups in references" table.
+In this sample:
+ - "Text" attribute must be set to *1* for capture "RETA_SRS_*NNNN*_*v* - *summary*",
+ - "Id" attribute must be set to *2* for capture "RETA_SRS_*NNNN*",
+ - "Version" attribute must be set to *3* for capture "*v*",
+ - "Summary" attribute must be set to *4* for capture "*summary*",
+
+Text and Id attributes are required to build the matrix.
+
+# Export
+
+RETA allows you to export the analysis result to an Excel file.
+
+# Alternatives to RETA
+
+- Reqtify (https://www.3ds.com/products/catia/reqtify)
\ No newline at end of file
diff --git a/RETA-api/pom.xml b/RETA-api/pom.xml
index 3a1d4d8..425e0a8 100644
--- a/RETA-api/pom.xml
+++ b/RETA-api/pom.xml
@@ -42,9 +42,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.11.2
RETA API JavaDocs
+ RETA ${project.version} ]]>
@@ -61,7 +61,7 @@
GNU General Public License (GPL)
- http://www.gnu.org/licenses/gpl-3.0.txt
+ ../LICENSE
\ No newline at end of file
diff --git a/RETA-api/src/main/java/com/ben12/reta/beans/property/validation/BeanPropertyValidation.java b/RETA-api/src/main/java/com/ben12/reta/beans/property/validation/BeanPropertyValidation.java
index 9b60c71..97a7ef3 100644
--- a/RETA-api/src/main/java/com/ben12/reta/beans/property/validation/BeanPropertyValidation.java
+++ b/RETA-api/src/main/java/com/ben12/reta/beans/property/validation/BeanPropertyValidation.java
@@ -44,8 +44,8 @@
*
* @param
* value type to validate
- * @see javax.validation.Validation
- * @see javax.validation.Validator
+ * @see jakarta.validation.Validation
+ * @see jakarta.validation.Validator
* @author Benoît Moreau (ben.12)
*/
public interface BeanPropertyValidation extends PropertyValidation
diff --git a/RETA-core/pom.xml b/RETA-core/pom.xml
index 6a70a86..3409e8d 100644
--- a/RETA-core/pom.xml
+++ b/RETA-core/pom.xml
@@ -10,9 +10,6 @@
reta-parent
1.1.0
-
- 5.4.0
-
ben.12
@@ -33,14 +30,6 @@
reta-api
-
-
- com.ben12
- reta-tika-plugin
- runtime
-
-
-
org.apache.poi
poi
@@ -56,6 +45,12 @@
poi-scratchpad
${poi.version}
+
+
+ net.sourceforge.plantuml
+ plantuml
+ 1.2025.2
+
@@ -91,9 +86,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.11.2
RETA CORE JavaDocs
+ RETA ${project.version} ]]>
@@ -110,7 +105,7 @@
GNU General Public License (GPL)
- http://www.gnu.org/licenses/gpl-3.0.txt
+ ../LICENSE
\ No newline at end of file
diff --git a/RETA-core/src/main/java/com/ben12/reta/Main.java b/RETA-core/src/main/java/com/ben12/reta/Main.java
index 24cda83..a85dcec 100644
--- a/RETA-core/src/main/java/com/ben12/reta/Main.java
+++ b/RETA-core/src/main/java/com/ben12/reta/Main.java
@@ -62,11 +62,14 @@ public void start(final Stage stage) throws Exception
loader.setResources(labels);
final Parent root = (Parent) loader.load();
+ final var title = labels.getString("title");
+ final var controller = (MainConfigurationController) loader.getController();
+ stage.titleProperty().bind(controller.bufferingProperty().map(b -> title + (b.booleanValue() ? " *" : "")));
+
stage.setScene(new Scene(root));
- stage.setTitle(labels.getString("title"));
stage.getIcons()
.add(new Image(Main.class.getResourceAsStream("/com/ben12/reta/resources/images/reta.png")));
- stage.sizeToScene();
+ stage.setMaximized(true);
stage.show();
final Parameters parameters = getParameters();
diff --git a/RETA-core/src/main/java/com/ben12/reta/export/ExcelExporter.java b/RETA-core/src/main/java/com/ben12/reta/export/ExcelExporter.java
index 8e351f7..f18ce0c 100644
--- a/RETA-core/src/main/java/com/ben12/reta/export/ExcelExporter.java
+++ b/RETA-core/src/main/java/com/ben12/reta/export/ExcelExporter.java
@@ -1,7 +1,7 @@
// Package : com.ben12.reta.export
// File : ExcelExporter.java
//
-// Copyright (C) 2025 benmo
+// Copyright (C) 2025 Benoît Moreau (ben.12)
//
// This file is part of RETA (Requirement Engineering Traceability Analysis).
//
@@ -452,7 +452,7 @@ private void exportCoverBy(final InputRequirementSource source, final XSSFSheet
{
final var rateRow = addNewRow(sheet, 1);
final var rateCell = addNewCell(rateRow);
- rateCell.setCellValue(source.getName() + " is cover by " + coverBy.getKey().getName() + " at "
+ rateCell.setCellValue(source.getName() + " is covered by " + coverBy.getKey().getName() + " at "
+ (coverBy.getValue() * 100) + " %");
addColspan(sheet, rateCell, 2);
diff --git a/RETA-core/src/main/java/com/ben12/reta/model/GraphData.java b/RETA-core/src/main/java/com/ben12/reta/model/GraphData.java
new file mode 100644
index 0000000..0c736b7
--- /dev/null
+++ b/RETA-core/src/main/java/com/ben12/reta/model/GraphData.java
@@ -0,0 +1,138 @@
+// Package : com.ben12.reta.model
+// File : GraphData.java
+//
+// Copyright (C) 2025 Benoît Moreau (ben.12)
+//
+// This file is part of RETA (Requirement Engineering Traceability Analysis).
+//
+// RETA is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RETA is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RETA. If not, see .
+package com.ben12.reta.model;
+
+import java.util.Map;
+
+/**
+ * @author Benoît Moreau (ben.12)
+ */
+public class GraphData
+{
+ private final String html;
+
+ private final Map sourceEntities;
+
+ private final Map links;
+
+ /**
+ * @param pHtml
+ * SVG source
+ * @param pSourceEntities
+ * requirement source alias
+ * @param pLinks
+ * links identifiers
+ */
+ public GraphData(final String pHtml, final Map pSourceEntities,
+ final Map pLinks)
+ {
+ this.html = pHtml;
+ this.sourceEntities = pSourceEntities;
+ this.links = pLinks;
+ }
+
+ /**
+ * @return the svg source
+ */
+ public String getHtml()
+ {
+ return html;
+ }
+
+ /**
+ * @return the requirement source alias
+ */
+ public Map getSourceEntities()
+ {
+ return sourceEntities;
+ }
+
+ /**
+ * @return the links identifiers
+ */
+ public Map getLinks()
+ {
+ return links;
+ }
+
+ public static class Link
+ {
+ private final String line;
+
+ private final InputRequirementSource source1;
+
+ private final RequirementImpl req1;
+
+ private final InputRequirementSource source2;
+
+ private final RequirementImpl req2;
+
+ public Link(final String pLine, final InputRequirementSource pSource1, final RequirementImpl pReq1,
+ final InputRequirementSource pSource2, final RequirementImpl pReq2)
+ {
+ this.line = pLine;
+ this.source1 = pSource1;
+ this.req1 = pReq1;
+ this.source2 = pSource2;
+ this.req2 = pReq2;
+
+ }
+
+ /**
+ * @return the line
+ */
+ public String getLine()
+ {
+ return line;
+ }
+
+ /**
+ * @return the source1
+ */
+ public InputRequirementSource getSource1()
+ {
+ return source1;
+ }
+
+ /**
+ * @return the req1
+ */
+ public RequirementImpl getReq1()
+ {
+ return req1;
+ }
+
+ /**
+ * @return the source2
+ */
+ public InputRequirementSource getSource2()
+ {
+ return source2;
+ }
+
+ /**
+ * @return the req2
+ */
+ public RequirementImpl getReq2()
+ {
+ return req2;
+ }
+ }
+}
diff --git a/RETA-core/src/main/java/com/ben12/reta/model/RequirementImpl.java b/RETA-core/src/main/java/com/ben12/reta/model/RequirementImpl.java
index 15d25a3..86e5273 100644
--- a/RETA-core/src/main/java/com/ben12/reta/model/RequirementImpl.java
+++ b/RETA-core/src/main/java/com/ben12/reta/model/RequirementImpl.java
@@ -59,7 +59,7 @@ public class RequirementImpl implements Requirement, Comparable
/** Requirement extra attributes name and value. */
private Map attributes = null;
- /** Set of requirements cover by this requirement. */
+ /** Set of requirements covered by this requirement. */
private Set references = null;
/** Set of requirements covering this requirement. */
@@ -306,7 +306,7 @@ public int getReferredByCount()
*/
public List getReferredByRequirement()
{
- return (referredBy == null ? new ArrayList(0) : new ArrayList(referredBy));
+ return (referredBy == null ? new ArrayList<>(0) : new ArrayList<>(referredBy));
}
/**
@@ -316,7 +316,7 @@ public List getReferredByRequirement()
*/
public List getReferredByRequirementFor(final InputRequirementSource aSource)
{
- return (referredBy == null ? new ArrayList(0)
+ return (referredBy == null ? new ArrayList<>(0)
: referredBy.stream().filter((r) -> r.getSource() == aSource).distinct().collect(Collectors.toList()));
}
@@ -325,9 +325,12 @@ public List getReferredByRequirementFor(final InputRequirementS
*/
public List getReferredBySource()
{
- return (referredBy == null ? new ArrayList(0)
- : referredBy.stream().map(RequirementImpl::getSource).filter(Objects::nonNull).distinct().collect(
- Collectors.toList()));
+ return (referredBy == null ? new ArrayList<>(0)
+ : referredBy.stream()
+ .map(RequirementImpl::getSource)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList()));
}
/*
diff --git a/RETA-core/src/main/java/com/ben12/reta/util/DOMUtils.java b/RETA-core/src/main/java/com/ben12/reta/util/DOMUtils.java
new file mode 100644
index 0000000..b66da59
--- /dev/null
+++ b/RETA-core/src/main/java/com/ben12/reta/util/DOMUtils.java
@@ -0,0 +1,65 @@
+// Package : com.ben12.reta.util
+// File : DOMUtils.java
+//
+// Copyright (C) 2025 Benoît Moreau (ben.12)
+//
+// This file is part of RETA (Requirement Engineering Traceability Analysis).
+//
+// RETA is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// RETA is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with RETA. If not, see .
+package com.ben12.reta.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * @author Benoît Moreau (ben.12)
+ */
+public class DOMUtils
+{
+ public static List getElementsByTagNameAndAttribute(final Document doc, final String tag,
+ final String attr)
+ {
+ final var elements = getElementsByTagName(doc, tag);
+ elements.removeIf(e -> !e.hasAttribute(attr));
+ return elements;
+ }
+
+ public static List getElementsByTagName(final Document doc, final String name)
+ {
+ final var nodeList = doc.getElementsByTagName(name);
+ return getElements(nodeList);
+ }
+
+ public static List getElementsByTagName(final Element el, final String name)
+ {
+ final var nodeList = el.getElementsByTagName(name);
+ return getElements(nodeList);
+ }
+
+ public static List getElements(final NodeList nodeList)
+ {
+ return IntStream.range(0, nodeList.getLength())
+ .mapToObj(nodeList::item)
+ .filter(n -> n instanceof Element)
+ .map(Element.class::cast)
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
+
+}
diff --git a/RETA-core/src/main/java/com/ben12/reta/util/RETAAnalysis.java b/RETA-core/src/main/java/com/ben12/reta/util/RETAAnalysis.java
index 87c187e..653a358 100644
--- a/RETA-core/src/main/java/com/ben12/reta/util/RETAAnalysis.java
+++ b/RETA-core/src/main/java/com/ben12/reta/util/RETAAnalysis.java
@@ -25,8 +25,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -44,8 +42,6 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import javafx.beans.property.SimpleStringProperty;
-import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -56,16 +52,10 @@
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
-import com.google.common.io.Files;
-
-import jakarta.validation.constraints.NotEmpty;
import com.ben12.reta.api.RETAParseException;
import com.ben12.reta.api.RETAParser;
import com.ben12.reta.api.SourceConfiguration;
-import com.ben12.reta.beans.constraints.IsPath;
-import com.ben12.reta.beans.constraints.PathExists;
-import com.ben12.reta.beans.constraints.PathExists.KindOfPath;
import com.ben12.reta.export.ExcelExporter;
import com.ben12.reta.model.InputRequirementSource;
import com.ben12.reta.model.RequirementImpl;
@@ -84,9 +74,6 @@ public final class RETAAnalysis
/** {@link #requirementSources} property name. */
public static final String REQUIREMENT_SOURCES = "requirementSources";
- /** {@link #output} property name. */
- public static final String OUTPUT = "output";
-
/** Singleton instance. */
private static RETAAnalysis instance = null;
@@ -99,13 +86,6 @@ public final class RETAAnalysis
/** Configuration file opened. */
private File config = null;
- /** Output Excel file path. */
- @NotEmpty
- @IsPath
- @PathExists(kind = KindOfPath.DIRECTORY, parent = true)
- private final StringProperty output = new SimpleStringProperty(this,
- OUTPUT);
-
// TODO maybe useful to see unknown references and mismatch versions
// private final Comparator reqCompId = (req1, req2) -> req1.getId().compareTo(req2.getId());
@@ -176,14 +156,6 @@ public File getConfig()
return config;
}
- /**
- * @return the output
- */
- public StringProperty outputProperty()
- {
- return output;
- }
-
/**
* @param iniFile
* RETA INI file
@@ -209,35 +181,6 @@ public void configure(final File iniFile)
ini.getConfig().setFileEncoding(Charset.forName("CP1252"));
ini.load(iniFile);
- final String outputFile = ini.get("GENERAL", "output");
- if (outputFile != null)
- {
- final Path outPath = Paths.get(outputFile);
- final String fileName = outPath.getFileName().toString();
- if (!"xlsx".equals(Files.getFileExtension(fileName)))
- {
- LOGGER.warning("output extension changed for xlsx");
- final Path parent = outPath.getParent();
- if (parent == null)
- {
- output.set(Files.getNameWithoutExtension(fileName) + ".xlsx");
- }
- else
- {
- output.set(parent.resolve(Files.getNameWithoutExtension(fileName) + ".xlsx").toString());
- }
- }
- else
- {
- output.set(outPath.normalize().toString());
- }
- }
- else
- {
- output.set(Paths.get(iniFile.getParent(), Files.getNameWithoutExtension(iniFile.getName()) + ".xlsx")
- .toString());
- }
-
final Map> coversMap = new LinkedHashMap<>();
final String documentsStr = ini.get("GENERAL", "inputs");
@@ -340,8 +283,6 @@ public boolean saveConfig(final File iniFile)
{
final Section generalSection = ini.add("GENERAL");
- generalSection.add("output", output.get());
-
final List inputs = new ArrayList<>();
for (final InputRequirementSource requirementSource : requirementSources)
@@ -446,9 +387,13 @@ public void parse(final InputRequirementSource requirementSource, final StringBu
/**
* Analyze the parsing result.
* Search requirement references in requirements.
+ *
+ * @param progress
+ * progression in percent
*/
- public void analyse()
+ public void analyse(final Consumer progress)
{
+ final var counter = new AtomicInteger(0);
requirementSources.parallelStream().forEach(source -> {
LOGGER.info("Start analyse " + source.getName());
@@ -490,6 +435,8 @@ public void analyse()
}
}
+ progress.accept((double) counter.incrementAndGet() / requirementSources.size());
+
LOGGER.info("End analyse " + source.getName());
});
}
@@ -497,22 +444,17 @@ public void analyse()
/**
* Write Excel file result of requirement traceability analysis.
*
+ * @param outputFile
+ * output file
* @throws IOException
* I/O exception
* @throws InvalidFormatException
* Invalid Excel format exception
*/
- public void writeExcel() throws IOException, InvalidFormatException
+ public void writeExcel(final File outputFile) throws IOException, InvalidFormatException
{
LOGGER.info("Start write excel output");
- Path outputFile = Paths.get(output.get());
- if (!outputFile.isAbsolute())
- {
- final Path root = config.getAbsoluteFile().getParentFile().toPath();
- outputFile = root.resolve(outputFile);
- }
-
final var exporter = new ExcelExporter();
final var wb = exporter.export(requirementSources);
writeExcel(wb, outputFile);
@@ -528,9 +470,9 @@ public void writeExcel() throws IOException, InvalidFormatException
* @throws IOException
* I/O exception
*/
- public void writeExcel(final Workbook workbook, final Path outputFile) throws IOException
+ public void writeExcel(final Workbook workbook, final File outputFile) throws IOException
{
- try (FileOutputStream fos = new FileOutputStream(outputFile.toFile()))
+ try (FileOutputStream fos = new FileOutputStream(outputFile))
{
workbook.write(fos);
}
diff --git a/RETA-core/src/main/java/com/ben12/reta/view/MainConfigurationController.java b/RETA-core/src/main/java/com/ben12/reta/view/MainConfigurationController.java
index 2fca0a1..9790dea 100644
--- a/RETA-core/src/main/java/com/ben12/reta/view/MainConfigurationController.java
+++ b/RETA-core/src/main/java/com/ben12/reta/view/MainConfigurationController.java
@@ -20,15 +20,19 @@
package com.ben12.reta.view;
import java.awt.Desktop;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.ResourceBundle;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@@ -36,28 +40,49 @@
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
+import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
+import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.util.Callback;
import javafx.util.Pair;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Objects;
+
+import net.sourceforge.plantuml.FileFormat;
+import net.sourceforge.plantuml.FileFormatOption;
+import net.sourceforge.plantuml.SourceStringReader;
+
import com.ben12.reta.beans.property.buffering.BufferingManager;
import com.ben12.reta.beans.property.buffering.ObservableListBuffering;
-import com.ben12.reta.beans.property.buffering.SimpleObjectPropertyBuffering;
+import com.ben12.reta.model.GraphData;
+import com.ben12.reta.model.GraphData.Link;
import com.ben12.reta.model.InputRequirementSource;
+import com.ben12.reta.model.RequirementImpl;
import com.ben12.reta.plugin.SourceProviderPlugin;
+import com.ben12.reta.util.DOMUtils;
import com.ben12.reta.util.RETAAnalysis;
import com.ben12.reta.view.control.MessageDialog;
import com.ben12.reta.view.validation.ValidationDecorator;
@@ -76,9 +101,6 @@ public class MainConfigurationController implements Initializable
/** The {@link BufferingManager} instance. */
private final BufferingManager bufferingManager = new BufferingManager();
- /** Output file buffered property. */
- private final SimpleObjectPropertyBuffering bufferedOutput;
-
/** Requirement source list. */
private final ObservableList sources = FXCollections
.observableArrayList();
@@ -106,6 +128,12 @@ public class MainConfigurationController implements Initializable
/** Requirement source {@link TitledPane} list. */
private List panes = new ArrayList<>();
+ /** Last export file. */
+ private File lastExport = null;
+
+ /** Graph data. */
+ private GraphData graph;
+
/** Root pane. */
@FXML
private Parent root;
@@ -126,6 +154,10 @@ public class MainConfigurationController implements Initializable
@FXML
private Button run;
+ /** Run analysis button. */
+ @FXML
+ private Button export;
+
/** Delete selected requirement source button. */
@FXML
private Button delete;
@@ -142,6 +174,14 @@ public class MainConfigurationController implements Initializable
@FXML
private ValidationDecorator outputFile;
+ /** TabPane result. */
+ @FXML
+ private TabPane resultTabs;
+
+ /** Graph result. */
+ @FXML
+ private WebView webview;
+
/**
* Constructor.
*/
@@ -156,7 +196,6 @@ public MainConfigurationController()
bufferingManager.add((ObservableListBuffering) bufferedSources);
bufferedSourcesName = bufferingManager.buffering(sourcesName);
- bufferedOutput = bufferingManager.buffering(RETAAnalysis.getInstance().outputProperty());
}
/**
@@ -206,9 +245,6 @@ public void initialize(final URL location, final ResourceBundle resources)
try
{
- outputFile.getChild().textProperty().bindBidirectional(bufferedOutput);
- outputFile.bindValidation(bufferedOutput);
-
delete.disableProperty()
.bind(sourceConfigurations.expandedPaneProperty()
.isNull()
@@ -250,6 +286,14 @@ public ObservableList> getDependencies()
save.disableProperty().bind(Bindings.not(bufferingManager.validProperty()));
cancel.disableProperty().bind(Bindings.not(bufferingManager.bufferingProperty()));
run.disableProperty().bind(Bindings.not(bufferingManager.validProperty()));
+ export.setDisable(true);
+
+ webview.getEngine().getLoadWorker().stateProperty().subscribe(state -> {
+ if (state == Worker.State.SUCCEEDED)
+ {
+ this.customizePlantuml();
+ }
+ });
}
catch (final Exception e)
{
@@ -257,6 +301,50 @@ public ObservableList> getDependencies()
}
}
+ private void customizePlantuml()
+ {
+ final var document = webview.getEngine().getDocument();
+ final var groups = DOMUtils.getElementsByTagNameAndAttribute(document, "g", "data-entity");
+ for (final var g : groups)
+ {
+ final var sourceAlias = g.getAttribute("data-entity");
+ final var source = graph.getSourceEntities().get(sourceAlias);
+ final var texts = DOMUtils.getElementsByTagName(g, "text");
+
+ if (!texts.isEmpty() && Objects.equal(source.getName(), texts.getFirst().getTextContent()))
+ {
+ final var textEl = texts.get(0);
+ final var title = document.createElementNS(textEl.getNamespaceURI(), "title");
+ title.setTextContent(source.getConfiguration().getDescription());
+ textEl.appendChild(title);
+ }
+
+ for (final Element textEl : texts.subList(1, texts.size()))
+ {
+ final var reqId = textEl.getTextContent();
+ final var req = source.getRequirements()
+ .stream()
+ .filter(r -> Objects.equal(r.getId(), reqId))
+ .findFirst();
+ if (req.isPresent())
+ {
+ final var title = document.createElementNS(textEl.getNamespaceURI(), "title");
+ title.setTextContent(req.get().getText());
+ textEl.appendChild(title);
+ if (req.get().getReferredBySource().isEmpty() && !source.getCoversBy().isEmpty())
+ {
+ textEl.setAttribute("style", "fill: red;");
+ }
+ else if (!req.get().getReferredBySource().isEmpty()
+ && req.get().getReferredBySource().size() < source.getCoversBy().size())
+ {
+ textEl.setAttribute("style", "fill: coral;");
+ }
+ }
+ }
+ }
+ }
+
/**
* Add the requirement source in the view.
*
@@ -470,24 +558,30 @@ protected void run(final ActionEvent event)
{
final var task = new Task()
{
+ private void updateProgress(final double p)
+ {
+ updateProgress(p, 1.0);
+ }
+
@Override
protected Void call() throws Exception
{
try
{
- updateProgress(0.00, 1.0);
+ updateProgress(0.00);
+
updateMessage(labels.getString("progress.reading"));
+ RETAAnalysis.getInstance().parse(p -> updateProgress(p * 0.50));
+ updateProgress(0.50);
- RETAAnalysis.getInstance().parse(p -> updateProgress(p * 0.60, 1.0));
- updateProgress(0.60, 1.0);
updateMessage(labels.getString("progress.analysing"));
+ RETAAnalysis.getInstance().analyse(p -> updateProgress(0.5 + (p * 0.10)));
+ updateProgress(0.60);
- RETAAnalysis.getInstance().analyse();
- updateProgress(0.70, 1.0);
- updateMessage(labels.getString("progress.writing"));
+ updateMessage(labels.getString("progress.graph"));
+ graph = buildGraph();
+ updateProgress(1.0);
- RETAAnalysis.getInstance().writeExcel();
- updateProgress(1.0, 1.0);
updateMessage(labels.getString("progress.complete"));
}
catch (final Exception e)
@@ -497,42 +591,325 @@ protected Void call() throws Exception
}
finally
{
- updateProgress(1.0, 1.0);
+ updateProgress(1.0);
}
return null;
}
+
+ @Override
+ protected void succeeded()
+ {
+ webview.getEngine().loadContent(graph.getHtml());
+ buildTabs();
+ export.setDisable(false);
+ }
+
+ @Override
+ protected void failed()
+ {
+ if (graph != null)
+ {
+ webview.getEngine().loadContent(graph.getHtml());
+ buildTabs();
+ }
+ }
};
MessageDialog.showProgressBar(root.getScene().getWindow(), labels.getString("progress.title"), task);
+ graph = null;
+ webview.getEngine().loadContent("");
+ resultTabs.getTabs().remove(1, resultTabs.getTabs().size());
+ export.setDisable(true);
+
new Thread(task).start();
}
}
- /**
- * Action event to select the output file.
- *
- * @param event
- * the {@link ActionEvent}
- */
+ private GraphData buildGraph()
+ {
+ final Map sourceEntities = new HashMap<>();
+ final Map links = new HashMap<>();
+ final var graphLines = new ArrayList();
+
+ graphLines.add("@startuml");
+ graphLines.add("skinparam classAttributeIconSize 0");
+
+ final var analysis = RETAAnalysis.getInstance();
+
+ final var index = new AtomicInteger(0);
+ final var allSources = analysis.requirementSourcesProperty();
+ final var isources = allSources.stream()
+ .collect(Collectors.toMap(s -> s, s -> index.getAndIncrement(), (a, b) -> a));
+
+ for (final var source : allSources)
+ {
+ final var i = isources.get(source);
+ graphLines.add("object \"**" + source.getName() + "**\" as S" + i + " {");
+ final var reqs = source.getRequirements();
+ for (final var req : reqs)
+ {
+ graphLines.add("{field} " + req.getId().replace("\\", "\\"));
+ }
+ graphLines.add("}");
+
+ sourceEntities.put("S" + i, source);
+ }
+
+ for (final var source : allSources)
+ {
+ final var i = isources.get(source);
+ final var reqs = source.getRequirements();
+ for (final var req : reqs)
+ {
+ final var reqId = req.getId().replace("\\", "\\");
+ final var refs = req.getReferences();
+ for (final var ref : refs)
+ {
+ final var refSource = ref.getSource();
+ if (refSource != null)
+ {
+ final var refSo = isources.get(refSource);
+ final var refId = ref.getId().replace("\\", "\\");
+ graphLines.add("\"S" + refSo + "::" + refId + "\" <--- \"S" + i + "::" + reqId + "\"");
+ }
+
+ final String dataSourceLine = "" + graphLines.size();
+ links.put(dataSourceLine, new Link(dataSourceLine, source, req, refSource, ref));
+ }
+ }
+ }
+
+ graphLines.add("@enduml");
+
+ final var puGraph = String.join("\n", graphLines);
+ final var reader = new SourceStringReader(puGraph);
+ final var output = new ByteArrayOutputStream();
+ try
+ {
+ reader.outputImage(output, new FileFormatOption(FileFormat.SVG));
+ }
+ catch (final IOException e)
+ {
+ LOGGER.log(Level.SEVERE, "Error during Graph generation", e);
+ }
+
+ final var svg = new String(output.toByteArray(), StandardCharsets.UTF_8);
+ final var html = """
+
+
+
+
+ """ + svg + """
+
+ """;
+ return new GraphData(html, sourceEntities, links);
+ }
+
+ private void buildTabs()
+ {
+ final var analysis = RETAAnalysis.getInstance();
+ final var allSources = analysis.requirementSourcesProperty();
+ for (final var source : allSources)
+ {
+ final var tab = createSourceTab(source);
+ resultTabs.getTabs().add(tab);
+ }
+
+ final var tab = createErrorsTab(allSources);
+ resultTabs.getTabs().add(tab);
+ }
+
+ private Tab createErrorsTab(final List allSources)
+ {
+ final var tab = new Tab(labels.getString("errors"));
+ tab.setClosable(false);
+
+ final var table = new GridPane();
+ table.getStyleClass().add("result-table");
+ table.setPadding(new Insets(8));
+
+ final var sourceHeader = new Label(labels.getString("unknownfromsrc"));
+ sourceHeader.getStyleClass().addAll("header", "first-row", "first-col");
+ final var reqHeader = new Label(labels.getString("unknownfromreq"));
+ reqHeader.getStyleClass().addAll("header", "first-row");
+ final var refHeader = new Label(labels.getString("unknownreference"));
+ refHeader.getStyleClass().addAll("header", "first-row", "last-col");
+ table.addRow(0, sourceHeader, reqHeader, refHeader);
+
+ final var total = allSources.stream().mapToInt(s -> s.getAllUknownReferences().size()).sum();
+
+ for (final var source : allSources)
+ {
+ final var reqs = source.getRequirements();
+ final var count = source.getAllUknownReferences().size();
+ if (count > 0)
+ {
+ var row = table.getRowCount();
+ final var sourceCell = new Label(source.getName());
+ sourceCell.getStyleClass().addAll("first-col");
+ table.add(sourceCell, 0, row, 1, count);
+ for (final var req : reqs)
+ {
+ final var refs = req.getReferencesFor(null);
+ if (!refs.isEmpty())
+ {
+ final var reqCell = new Label(req.getText());
+ table.add(reqCell, 1, row, 1, refs.size());
+ for (final var ref : refs)
+ {
+ final var refCell = new Label(ref.getText());
+ refCell.getStyleClass().addAll("last-col");
+ table.add(refCell, 2, row);
+ if (row == total)
+ {
+ sourceCell.getStyleClass().addAll("last-row");
+ reqCell.getStyleClass().addAll("last-row");
+ refCell.getStyleClass().addAll("last-row");
+ }
+ row++;
+ }
+ }
+ }
+ }
+ }
+
+ tab.setContent(new ScrollPane(table));
+ return tab;
+ }
+
@FXML
- protected void selectOutputFile(final ActionEvent event)
+ protected void export(final ActionEvent event)
{
- final Path currentFile = bufferedOutput.get() == null ? null : Paths.get(bufferedOutput.get());
- final FileChooser fileChooser = new FileChooser();
- fileChooser.getExtensionFilters().add(new ExtensionFilter(labels.getString("excel.file.desc"), "*.xlsx"));
- fileChooser.setTitle(labels.getString("output.title"));
- if (currentFile != null)
+ try
+ {
+ final FileChooser fileChooser = new FileChooser();
+ fileChooser.getExtensionFilters().add(new ExtensionFilter(labels.getString("excel.file.desc"), "*.xlsx"));
+ fileChooser.setTitle(labels.getString("output.title"));
+ if (lastExport != null)
+ {
+ fileChooser.setInitialDirectory(lastExport.getParentFile());
+ fileChooser.setInitialFileName(lastExport.getName());
+ }
+ else
+ {
+ fileChooser.setInitialDirectory(RETAAnalysis.getInstance().getConfig().getParentFile());
+ }
+
+ final File file = fileChooser.showSaveDialog(root.getScene().getWindow());
+ if (file != null)
+ {
+ RETAAnalysis.getInstance().writeExcel(file);
+ lastExport = file;
+ }
+ }
+ catch (InvalidFormatException | IOException e)
{
- fileChooser.setInitialDirectory(currentFile.toFile().getParentFile());
- fileChooser.setInitialFileName(currentFile.getFileName().toString());
+ LOGGER.log(Level.SEVERE, "Exporting excel", e);
}
- final File file = fileChooser.showSaveDialog(root.getScene().getWindow());
+ }
- if (file != null)
+ private Tab createSourceTab(final InputRequirementSource source)
+ {
+ final var tab = new Tab(source.getName());
+ tab.setClosable(false);
+
+ final var table = new GridPane();
+ table.getStyleClass().add("result-table");
+ table.setPadding(new Insets(8));
+
+ final var reqHeader = new Label(labels.getString("requirement"));
+ reqHeader.getStyleClass().addAll("header", "first-row", "first-col");
+ final var sourceHeader = new Label(labels.getString("source"));
+ sourceHeader.getStyleClass().addAll("header", "first-row");
+ final var refHeader = new Label(labels.getString("reference"));
+ refHeader.getStyleClass().addAll("header", "first-row", "last-col");
+
+ table.addRow(0, reqHeader, sourceHeader, refHeader);
+
+ final var requirements = source.getRequirements();
+ final var refSources = source.getCoversBy().keySet();
+ var index = 0;
+ for (final var requirement : requirements)
+ {
+ final var reqCell = new Label(requirement.getText());
+ reqCell.getStyleClass().add("first-col");
+ setReqStyle(source, requirement, reqCell);
+
+ final List
+
+ GitHub
+ https://github.com/ben12/reta/issues
+
+
+ https://github.com/ben12/reta/tree/master/RETA-packaging
+ scm:git:git://github.com/ben12/reta.git/RETA-packaging
+ scm:git:git@github.com:ben12/reta.git/RETA-packaging
+
+
+
+ GNU General Public License (GPL)
+ ../LICENSE
+
+
\ No newline at end of file
diff --git a/RETA-tika-plugin/pom.xml b/RETA-tika-plugin/pom.xml
index fb5f4f7..e18f3ee 100644
--- a/RETA-tika-plugin/pom.xml
+++ b/RETA-tika-plugin/pom.xml
@@ -10,10 +10,6 @@
reta-parent
1.1.0
-
- 3.1.0
- 5.4.0
-
ben.12
@@ -78,9 +74,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.11.2
RETA TIKA plugin JavaDocs
+ RETA ${project.version} ]]>
@@ -97,7 +93,7 @@
GNU General Public License (GPL)
- http://www.gnu.org/licenses/gpl-3.0.txt
+ ../LICENSE
\ No newline at end of file
diff --git a/data/analysis.reta b/data/analysis.reta
index c0e45f3..d13c224 100644
--- a/data/analysis.reta
+++ b/data/analysis.reta
@@ -1,45 +1,40 @@
-[GENERAL]
-output = analysis.xlsx
-inputs = DOC1,DOC2,SOURCES
-
-[DOC1]
-plugin = com.ben12.reta.plugin.tika.TikaSourceProviderPlugin
-path = DocTest1.doc
-filter =
-requirement.start.regex = ^[ \t]*REQ_(RETA_(\w+_\d+)(?:_(\w+))?[\s-]+(.*))$
-requirement.start.Version.index = 3
-requirement.start.Label.index = 4
-requirement.start.Text.index = 1
-requirement.start.Id.index = 2
-requirement.end.regex = ^[ \t]*END_REQ[ \t]*$
-requirement.ref.regex = ^[ \t]*CF_REQ_RETA_(\w+_\d+)(?:_(\w+))?(?:[ \t-]+(.*))?$
-requirement.ref.Comment.index = 3
-requirement.ref.Version.index = 2
-requirement.ref.Id.index = 1
-covers = DOC2
-
-[DOC2]
-plugin = com.ben12.reta.plugin.tika.TikaSourceProviderPlugin
-path = DocTest2.doc
-filter =
-requirement.start.regex = ^[ \t]*REQ_RETA_(\w+_\d+)(?:_(\w+))?[\s-]+(.*)$
-requirement.start.Version.index = 2
-requirement.start.Label.index = 3
-requirement.start.Text.index = 0
-requirement.start.Id.index = 1
-requirement.end.regex = ^END_REQ
-requirement.ref.regex = ^[ \t]*CF_REQ_RETA_(\w+_\d+)(?:_(\w+))?(?:[ \t-]+(.*))?$
-requirement.ref.Comment.index = 3
-requirement.ref.Version.index = 2
-requirement.ref.Id.index = 1
-covers = DOC1
-
-[SOURCES]
-plugin = com.ben12.reta.plugin.tika.TikaDirectorySourceProviderPlugin
-path = sourcesTests
-filter = ^(include\\.*\.(h|hpp)|java\\.*\.java|[^\\]*\.(c|cpp))$
-requirement.ref.regex = CF_(?:REQ_)?RETA_(\w+_\d+)(?:_(\w+))?
-requirement.ref.Version.index = 2
-requirement.ref.Id.index = 1
-covers = DOC1,DOC2
-
+[GENERAL]
+inputs = DOC1,DOC2,SOURCES
+
+[DOC1]
+plugin = com.ben12.reta.plugin.tika.TikaSourceProviderPlugin
+path = DocTest1.doc
+filter =
+requirement.start.regex = ^[ \t]*REQ_(RETA_(\w+_\d+)(?:_(\w+))?[\s-]+(.*))$
+requirement.start.Version.index = 3
+requirement.start.Label.index = 4
+requirement.start.Text.index = 1
+requirement.start.Id.index = 2
+requirement.end.regex = ^[ \t]*END_REQ[ \t]*$
+covers =
+
+[DOC2]
+plugin = com.ben12.reta.plugin.tika.TikaSourceProviderPlugin
+path = DocTest2.doc
+filter =
+requirement.start.regex = ^[ \t]*REQ_RETA_(\w+_\d+)(?:_(\w+))?[\s-]+(.*)$
+requirement.start.Version.index = 2
+requirement.start.Label.index = 3
+requirement.start.Text.index = 0
+requirement.start.Id.index = 1
+requirement.end.regex = ^END_REQ
+requirement.ref.regex = ^[ \t]*CF_REQ_RETA_(\w+_\d+)(?:_(\w+))?(?:[ \t-]+(.*))?$
+requirement.ref.Comment.index = 3
+requirement.ref.Version.index = 2
+requirement.ref.Id.index = 1
+covers = DOC1
+
+[SOURCES]
+plugin = com.ben12.reta.plugin.tika.TikaDirectorySourceProviderPlugin
+path = sourcesTests
+filter = ^(include\\.*\.(h|hpp)|java\\.*\.java|[^\\]*\.(c|cpp))$
+requirement.ref.regex = CF_(?:REQ_)?RETA_(\w+_\d+)(?:_(\w+))?
+requirement.ref.Version.index = 2
+requirement.ref.Id.index = 1
+covers = DOC1,DOC2
+
diff --git a/eclipse/launchers/RETA Main.launch b/eclipse/launchers/RETA Main.launch
index 96fca47..fd4d510 100644
--- a/eclipse/launchers/RETA Main.launch
+++ b/eclipse/launchers/RETA Main.launch
@@ -13,7 +13,7 @@
-
+
@@ -22,6 +22,6 @@
-
+
diff --git a/pom.xml b/pom.xml
index 2962289..0e860d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,13 +6,15 @@
pom
1.1.0
Requirement Engineering Traceability Analysis
- https://github.com/ben12/reta
+ https://reta.ben12.eu
Requirement Engineering Traceability Analysis Project
cp1252
21
21
21
+ 3.1.0
+ 5.4.0
@@ -45,6 +47,11 @@
javafx-fxml
${javafx.version}
+
+ org.openjfx
+ javafx-web
+ ${javafx.version}
+
com.google.guava
guava
@@ -103,7 +110,7 @@
RETA
- http://ben12.github.io/reta
+ https://reta.ben12.eu
@@ -114,6 +121,11 @@
maven-compiler-plugin
3.14.0
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.11.2
+
org.apache.maven.plugins
maven-site-plugin
@@ -137,7 +149,7 @@
GNU General Public License (GPL)
- http://www.gnu.org/licenses/gpl-3.0.txt
+ ./LICENSE
\ No newline at end of file
diff --git a/site/markdown/index.md b/site/markdown/index.md
new file mode 100644
index 0000000..d423495
--- /dev/null
+++ b/site/markdown/index.md
@@ -0,0 +1,50 @@
+## About
+
+RETA (Requirement Engineering Traceability Analysis) can read any source of documents or code, extracts the requirements and requirement references, and analyses them for generate the traceability matrix.
+
+## Installation
+
+Download then install [the last release](https://github.com/ben12/reta/releases/latest).
+
+## Plugins
+
+### TIKA plugin
+
+TIKA plugin uses [Apache Tika](https://tika.apache.org/) to read any document (doc, xls, pdf, ...) and extracts requirements and requirement references from it.
+
+#### Configuration
+
+Requirements and references are extracted from documents using regular expression. You can find documentation on the web and for example here : [Pattern JAVADOC](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/regex/Pattern.html#construct)
+
+So, if your requirements are formatted like this in your document :
+
+> REQ_RETA_SRS_*NNNN*_*v* - *summary*
+>
+> *content*
+>
+> REQ_END
+
+Where "NNNN" is the requirement number, "v" the version, "summary" the summary description and "content" the detailed description.
+
+Then, regular expression to match requirement start may be :
+ `^[ \t]*REQ_((RETA_SRS_\d+)_(\w+)[\s-]+(.*?))$`
+And regular expression to match requirement end may be :
+ `^[ \t]*REQ_END[ \t]*$`
+
+Bracket will capture part of text which will be used, for example, for identify the requirement reference using "Id" attribute ([Pattern JAVADOC](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/regex/Pattern.html#cg)).
+For done this, you must identify each capture group using "Index of regular expression groups in requirement starts" table and "Index of regular expression groups in references" table.
+In this sample:
+ - "Text" attribute must be set to *1* for capture "RETA_SRS_*NNNN*_*v* - *summary*",
+ - "Id" attribute must be set to *2* for capture "RETA_SRS_*NNNN*",
+ - "Version" attribute must be set to *3* for capture "*v*",
+ - "Summary" attribute must be set to *4* for capture "*summary*",
+
+Text and Id attributes are required to build the matrix.
+
+## Export
+
+RETA allows you to export the analysis result to an Excel file.
+
+## Alternatives to RETA
+
+- Reqtify (https://www.3ds.com/products/catia/reqtify)
\ No newline at end of file
diff --git a/site/site.xml b/site/site.xml
index c07eeb1..091d3dc 100644
--- a/site/site.xml
+++ b/site/site.xml
@@ -1,17 +1,21 @@
-
org.apache.maven.skins
maven-fluido-skin
- 1.4
+ 2.1.0
-
+ true
+ true
+
+
+
ben12/reta
right