Commit 748b76ca authored by Martti Käärik's avatar Martti Käärik
Browse files

Read constraint severities from config file and add to OCL in Ecore tuples +...

Read constraint severities from config file and add to OCL in Ecore tuples + same format for separate OCL (interpreted mode) + drop error/warning wrappers.
parent 7e0d88b3
Loading
Loading
Loading
Loading
+57 −0
Changes for Global_Summary_Report_2026.md: 57 added lines, 0 removed lines.
Original line number Diff line number Diff line
# TOP IDE Global Summary Report (Sept 2025 - Feb 2026)

## Executive Overview
Over the past six months, the ETSI TOP IDE has undergone a significant modernization and feature expansion phase, primarily driven by the **TTF::045** task force. The project successfully migrated to **Java 21**, implemented foundational support for **ASN.1 and YANG**, and introduced a new **TTCN-3 rendering engine**.

---

## 1. Major Technical Milestones

### **Java 21 & Infrastructure Modernization**
*   **Java 21 Migration:** The entire IDE stack, target platforms, and build configurations were updated to Java 21 (#147).
*   **Dependency Decoupling:** The monolithic `TDLHelper` was decoupled to reduce artifact sizes and improve modularity.
*   **Build Reliability:** Fixed several CI/CD pipeline issues and OCL dependency resolution bugs.

### **Core Language & Meta-model Evolution**
*   **Syntactic Ambiguity Resolution:** Resolved critical ambiguities in `CastDataUse` and `DataElementUse` that were hindering code completion.
*   **New Language Features:** Added direct support for negative numbers (negation) and refined OCL constraints for complex behaviors.

---

## 2. Key Feature Progress

### **Conversion & Interoperability**
*   **TP to TD Conversion (Epic #34):** Developed a new UI-driven mechanism to convert Test Purposes (TP) into Test Descriptions (TD), including identifier substitution and traceability options.
*   **TTCN-3 Rendering (Epic #27):** Implemented a new, standalone Java-based renderer to translate TDL models into TTCN-3 test cases, supporting complex mappings like reductions and extensions.
*   **ASN.1 & YANG Support:** Validated the `ASN1C` and `pycrate` tools and added dedicated TDL type libraries for ASN.1 and YANG definitions.

### **User Experience (UX) & Editor Intelligence**
*   **Semantic Editor:** Introduced semantic highlighting for events and improved content assist for complex data structures.
*   **Performance:** Implemented a validation toggle and refactored the validator to prevent UI freezes during intensive editing sessions.
*   **Rich Hover:** Enhanced hover providers to show recursive structural data for instances and message arguments.

---

## 3. Strategic & Methodological Insights (from TTF Notes)

*   **TDL Philosophy:** Maintained a focus on "strong typing," leading to discussions on requiring explicit type-casting for inferred parameters.
*   **Abstract States:** Consensus reached on reusing `Initial` and `Final` annotations for verdict handling, avoiding unnecessary meta-model bloat.
*   **Local Scope Variables:** Decision to prioritize component-bound variables over behavior-local variables to keep the language manageable for users.
*   **Default Timers:** Move toward using annotations for wait timers to keep the core specification lightweight while meeting user requirements for timeout management.

---

## 4. Quantitative Performance
*   **Commits:** 85+
*   **Issues Resolved:** 30+ (Project Level)
*   **Epics with Active Progress:** 16 (Group Level)
*   **New Priority Items:** 25

---

## 5. Roadmap & Future Outlook (2026)
*   **Documentation:** Prioritizing the TR and Wiki updates for ASN.1 and OpenAPI importers.
*   **VS Code Support:** Preliminary work has begun on exploring a TDL plugin for the Visual Studio Code platform (Epic #32).
*   **Validation Rigor:** Further implementation of mandatory/optional field checks at the execution level.

**Conclusion:** The project is in a strong state of transition from a modeling-centric tool to a robust, execution-ready IDE integrated into modern CI/CD workflows.
+1 −22
Changes for plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/CompleteOclReader.java: 1 added line, 22 removed lines.
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ public final class CompleteOclReader {
	private static final Pattern CONTEXT = Pattern.compile("^context\\s+(\\w+)\\s*$");
	private static final Pattern KEYWORD = Pattern.compile("(def|package|endpackage|import)\\b");
	private static final Pattern INV = Pattern.compile("^\\s*inv\\s+(\\w+)\\s*(?:\\('((?:[^'\\\\]|\\\\.)*)'\\s*\\+\\s*self\\.(?:toString|trace)\\(\\)\\))?\\s*:\\s*$"); // trace(): hand-edited form in the repo .ocl
	private static final Pattern WRAPPER = Pattern.compile("^\\s*(error|warning)\\((.*)\\)\\s*$", Pattern.DOTALL);

	private CompleteOclReader() {
	}
@@ -81,26 +80,6 @@ public final class CompleteOclReader {
		if (context == null || name == null || expression == null || expression.length() == 0) {
			return;
		}
		String text = expression.toString().trim();
		Matcher w = WRAPPER.matcher(text);
		if (w.matches() && balanced(w.group(2))) {
			text = w.group(2).trim();
		}
		records.add(new ConstraintRecord(context, name, title, description, text));
	}

	/** True when the parentheses in {@code s} are balanced (so the outer wrapper really encloses all of it). */
	private static boolean balanced(String s) {
		int depth = 0;
		for (char ch : s.toCharArray()) {
			if (ch == '(') {
				depth++;
			} else if (ch == ')') {
				if (--depth < 0) {
					return false;
				}
			}
		}
		return depth == 0;
		records.add(new ConstraintRecord(context, name, title, description, expression.toString().trim()));
	}
}
+55 −2
Changes for plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/ConstraintExporter.java: 55 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ public class ConstraintExporter {
		String generatedPackage = "tdl";
		String ecore = null;
		java.util.Set<String> excluded = new java.util.HashSet<>();
		String severities = null;
		String interpretedOcl = null;

		for (int i = 0; i < args.length; i++) {
			if (args[i].equalsIgnoreCase("--format") && args.length > i + 1) {
@@ -61,6 +63,10 @@ public class ConstraintExporter {
				ecore = args[i + 1];
			if (args[i].equalsIgnoreCase("--exclude") && args.length > i + 1)
				excluded.addAll(java.util.Arrays.asList(args[i + 1].split(",")));
			if (args[i].equalsIgnoreCase("--severities") && args.length > i + 1)
				severities = args[i + 1];
			if (args[i].equalsIgnoreCase("--interpreted-ocl") && args.length > i + 1)
				interpretedOcl = args[i + 1];
		}

		ConstraintExporter exporter = new ConstraintExporter();
@@ -88,18 +94,65 @@ public class ConstraintExporter {
		if (format == OutputFormat.ECORE) {
			if (ecore == null)
				throw new IllegalArgumentException("--format ecore requires --ecore <path to .ecore>");
			java.util.Map<String, Integer> severityOf = severities == null ? java.util.Collections.emptyMap() : readSeverities(Path.of(severities));
			java.util.Set<String> unused = new java.util.TreeSet<>(severityOf.keySet());
			// Invariants the OCL code generator cannot compile (Class::Name) are kept in the Complete OCL text only (temporary)
			java.util.List<ConstraintRecord> compilable = new java.util.ArrayList<>();
			for (ConstraintRecord r : constraints) {
				unused.remove(r.toString());
				if (excluded.contains(r.toString()))
					System.out.println("Excluded from ecore (Java fallback): " + r);
				else
					compilable.add(r);
					compilable.add(r.withSeverity(severityOf.get(r.toString())));
			}
			// one severities file serves all metamodels
			java.util.Set<String> classes = EcoreConstraintEmitter.classNames(Path.of(ecore));
			unused.removeIf(id -> !classes.contains(id.substring(0, id.indexOf("::"))));
			if (!unused.isEmpty())
				System.out.println("Severities for invariants that " + filename + " does not have: " + String.join(", ", unused));
			EcoreConstraintEmitter.Report report = EcoreConstraintEmitter.apply(Path.of(ecore), compilable);
			System.out.println("Wrote invariants into " + ecore);
			System.out.print(report);
		}
			if (interpretedOcl != null) {
				if (!filename.endsWith(".ocl"))
					throw new IllegalArgumentException("--interpreted-ocl requires a Complete OCL document as --in");
				java.util.List<String> rewritten = InterpretedOclWriter.write(Path.of(filename), Path.of(interpretedOcl), severityOf);
				System.out.println("Wrote the interpreted document " + interpretedOcl + (rewritten.isEmpty() ? "" : ", with severities: " + String.join(", ", rewritten)));
			}
		}
	}

	/**
	 * Lines {@code Class::Name = error|warning|info} as the OCL runtime's severity (-1, 1, 0); {@code #} starts a comment.
	 */
	static java.util.Map<String, Integer> readSeverities(Path file) throws IOException {
		java.util.Map<String, Integer> result = new java.util.HashMap<>();
		for (String line : Files.readAllLines(file, java.nio.charset.StandardCharsets.UTF_8)) {
			line = line.replaceAll("#.*$", "").trim();
			if (line.isEmpty()) {
				continue;
			}
			int eq = line.indexOf('=');
			if (eq < 0) {
				throw new IllegalArgumentException(file + ": " + line + " (expected Class::Name = error|warning|info)");
			}
			String id = line.substring(0, eq).trim();
			String value = line.substring(eq + 1).trim().toLowerCase();
			switch (value) {
			case "error":
				result.put(id, -1);
				break;
			case "warning":
				result.put(id, 1);
				break;
			case "info":
				result.put(id, 0);
				break;
			default:
				throw new IllegalArgumentException(file + ": " + id + " = " + value + " (expected error, warning or info)");
			}
		}
		return result;
	}

	public void exportGrammar(Document doc, String generatedPackage, String[] extraImports, String generatedConstraintsPath, boolean skipSemantics) {
+9 −0
Changes for plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/ConstraintRecord.java: 9 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -14,11 +14,20 @@ public final class ConstraintRecord {
	public final Integer severity;

	public ConstraintRecord(String className, String name, String title, String description, String expression) {
		this(className, name, title, description, expression, null);
	}

	private ConstraintRecord(String className, String name, String title, String description, String expression, Integer severity) {
		this.className = className;
		this.name = name;
		this.title = title == null ? "" : title;
		this.description = description == null ? "" : description;
		this.expression = expression;
		this.severity = severity;
	}

	public ConstraintRecord withSeverity(Integer severity) {
		return new ConstraintRecord(className, name, title, description, expression, severity);
	}

	/** The OCL message expression used by both the Complete OCL and the OCLinEcore output. */
+18 −2
Changes for plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/EcoreConstraintEmitter.java: 18 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -77,12 +77,28 @@ public final class EcoreConstraintEmitter {
	private EcoreConstraintEmitter() {
	}

	public static Report apply(Path ecoreFile, List<ConstraintRecord> records) throws IOException {
	/** The names of the classes of the metamodel, its subpackages included. */
	public static java.util.Set<String> classNames(Path ecoreFile) {
		java.util.Set<String> names = new java.util.HashSet<>();
		for (java.util.Iterator<org.eclipse.emf.ecore.EObject> it = load(ecoreFile).getAllContents(); it.hasNext();) {
			org.eclipse.emf.ecore.EObject o = it.next();
			if (o instanceof EClass) {
				names.add(((EClass) o).getName());
			}
		}
		return names;
	}

	private static Resource load(Path ecoreFile) {
		ResourceSet rs = new ResourceSetImpl();
		rs.setURIConverter(new ExtensibleURIConverterImpl());
		rs.getResourceFactoryRegistry().getExtensionToFactoryMap().put("ecore", new EcoreResourceFactoryImpl());
		rs.getPackageRegistry().put(EcorePackage.eNS_URI, EcorePackage.eINSTANCE);
		Resource resource = rs.getResource(URI.createFileURI(ecoreFile.toAbsolutePath().toString()), true);
		return rs.getResource(URI.createFileURI(ecoreFile.toAbsolutePath().toString()), true);
	}

	public static Report apply(Path ecoreFile, List<ConstraintRecord> records) throws IOException {
		Resource resource = load(ecoreFile);
		EPackage ePackage = (EPackage) resource.getContents().get(0);

		Report report = new Report();
Loading