Loading plugins/org.etsi.mts.tdl.constraints.helper/README.md +58 −4 Changes for plugins/org.etsi.mts.tdl.constraints.helper/README.md: 58 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -10,7 +10,8 @@ The implementation of the tool makes use of the Apache POI and related libraries ## Command line ConstraintExporter --format evl|ocl|ecore --in <file> [--ecore <file>] [--exclude Class::Name,...] ConstraintExporter --format evl|ocl|ecore --in <file> [--ecore <file>] [--severities <file>] [--interpreted-ocl <file>] [--compare-ocl <file>] [--exclude Class::Name,...] [--import <uri>,...] [--generated-package <name>] Classpath: the compiled classes, `lib/*` and the plug-ins of an Eclipse Modeling installation (for EMF). Loading @@ -20,14 +21,67 @@ Classpath: the compiled classes, `lib/*` and the plug-ins of an Eclipse Modeling - `--format ocl` (and `evl`) writes `resources/generated/tdl-generated-<title>.ocl`. - `--format ecore` additionally writes the invariants into the metamodel given with `--ecore`, in the form the OCLinEcore code generator compiles: per class the `constraints` list of the Ecore annotation, and per invariant the details `<Name>` (the expression) and `<Name>$message` (the description of the constraint followed by `self.toString()`). `error(...)` and `warning(...)` wrappers around an expression are dropped. The file is written with the EMF API, and per invariant one detail `<Name>` of the Pivot annotation holding the tuple-valued invariant of Eclipse OCL: Tuple { message : String = '<description of the constraint>' + self.toString(), severity : Integer = -1, status : Boolean = <expression> }.status The expression is copied as is, a line break before the closing brace is there because an expression may end in a `--` comment. The `severity` part is only written for invariants listed in the `--severities` file. The deprecated `<Name>$message` detail, which Eclipse OCL converts to this form on load, is removed when found. The file is written with the EMF API, so it keeps the formatting of the Ecore editor. The invariants that are no longer in the input are removed from the classes that are in the input. Operation bodies are not touched. A report lists what was added, updated, left unchanged and removed, the classes it did not find, and the invariants that use `allInstances()`. - `--severities` names a file of lines `Class::Name = error|warning|info` (`#` starts a comment) (`model/constraint-severities.properties` in the model plug-in), written as the `severity` part (-1, 1, 0). - `--interpreted-ocl` writes the Complete OCL export (from `--format ocl` or `ecore`) once more, with the invariants that have a configured severity in the same tuple form: what the deployed document of the interpreted validator should contain. Conventionally `model/interpreted/<document>.ocl` in the model plug-in. It is not loaded, acts as the reference for the review. - `--compare-ocl` compares the export (the `--interpreted-ocl` one if given) with a deployed Complete OCL document, per invariant and ignoring whitespace: expressions that differ, invariants missing on either side. Nothing is written. - `--exclude` keeps the named invariants out of the metamodel (they stay in the OCL output), for invariants the code generator cannot compile. Writing the invariants into a metamodel makes the EMF generator produce a validator for it. ## Workflow The specification is canonical - each part is exported on its own (one run per document): | Part | Document | Package options | Metamodel | Deployed OCL | |---|---|---|---|---| | 1 | `es_20311901…docx` | (default `tdl`) | `tdl.ecore` | `tdl-constraints.ocl` | | 4 | `es_20311904…docx` | `--generated-package structuredobjectives --import http://www.etsi.org/spec/TDL/1.3.1/structured` | `structured.ecore` | `tdl-structured-constraints.ocl` | | 7 | `es_20311907…docx` | `--generated-package extendedconfigurations --import http://www.etsi.org/spec/TDL/1.3.1/configurations` | `configurations.ecore` | `tdl-configurations-constraints.ocl` | Metamodel (compiled constraints, the default), from the plug-in folder: ConstraintExporter --format ecore --in <part 1 .docx> --ecore ../org.etsi.mts.tdl.model/model/tdl.ecore --severities ../org.etsi.mts.tdl.model/model/constraint-severities.properties Review of the deployed Complete OCL documents (interpreted constraints, being deprecated; they are maintained by hand): ConstraintExporter --format ocl --in <part 1 .docx> --severities ../org.etsi.mts.tdl.model/model/constraint-severities.properties --interpreted-ocl ../org.etsi.mts.tdl.model/model/interpreted/tdl-constraints.ocl --compare-ocl ../org.etsi.mts.tdl.model/model/tdl-constraints.ocl then correct the specification or the deployed document (porting from the export) and run again until the comparison reports no differences. Upgrade idea: apply the severities at runtime instead of at export, in the interpreted validator (`Validator` in the model plug-in: wrap the `DiagnosticChain` in `validatePivot` and set the severity of the diagnostics of configured constraints, recognised by their description) and, for the compiled invariants, through the OCL executor's severity hook or the Xtext diagnostic converter. One mechanism for both implementations, severities changeable without a re-export, and nothing to port by hand into the deployed documents; the cost is matching diagnostics back to constraints by message. plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/ConstraintExporter.java +58 −4 Changes for plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/ConstraintExporter.java: 58 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ public class ConstraintExporter { java.util.Set<String> excluded = new java.util.HashSet<>(); String severities = null; String interpretedOcl = null; String compareOcl = null; for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("--format") && args.length > i + 1) { Loading @@ -67,19 +68,25 @@ public class ConstraintExporter { severities = args[i + 1]; if (args[i].equalsIgnoreCase("--interpreted-ocl") && args.length > i + 1) interpretedOcl = args[i + 1]; if (args[i].equalsIgnoreCase("--compare-ocl") && args.length > i + 1) compareOcl = args[i + 1]; } ConstraintExporter exporter = new ConstraintExporter(); exporter.format = format; java.util.List<ConstraintRecord> constraints; // the Complete OCL text of the constraints: the input itself, or the export of the specification Path oclText; if (filename.endsWith(".ocl")) { // A Complete OCL document as input, only the ecore target makes sense. constraints = CompleteOclReader.read(Path.of(filename)); oclText = Path.of(filename); System.out.println("Read " + constraints.size() + " invariants from " + filename); } else { Document doc = DocumentHandler.loadDocument(new File(filename).getAbsolutePath()); String title = doc.getTitle().replaceAll(" ", "-"); String targetPath = "resources/generated/tdl-generated-" + title + (format == OutputFormat.EVL ? ".evl" : ".ocl"); oclText = format == OutputFormat.EVL ? null : Path.of(targetPath); exporter.exportConstraints(doc, generatedPackage, extraImports, targetPath); exporter.exportModel(doc, generatedPackage, extraImports, "resources/generated/tdl-generated-" + title + ".txt", true); constraints = exporter.getRecords(); Loading @@ -91,10 +98,10 @@ public class ConstraintExporter { exporter.exportGrammar(grammarDoc, generatedPackage, extraImports, "resources/generated/tdl-generated-" + title + "-grammar.txt", true); } } java.util.Map<String, Integer> severityOf = severities == null ? java.util.Collections.emptyMap() : readSeverities(Path.of(severities)); 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<>(); Loading @@ -113,13 +120,60 @@ public class ConstraintExporter { EcoreConstraintEmitter.Report report = EcoreConstraintEmitter.apply(Path.of(ecore), compilable); System.out.println("Wrote invariants into " + ecore); System.out.print(report); } // Review of a deployed document: the export (with the severities) is written next to it and compared with it. // Either the specification or the deployed document is corrected until they agree. if ((interpretedOcl != null || compareOcl != null) && oclText == null) throw new IllegalArgumentException("--interpreted-ocl and --compare-ocl need a Complete OCL export (--format ocl or ecore)"); Path reference = oclText; 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); java.util.List<String> rewritten = InterpretedOclWriter.write(oclText, Path.of(interpretedOcl), severityOf); reference = Path.of(interpretedOcl); System.out.println("Wrote the interpreted document " + interpretedOcl + (rewritten.isEmpty() ? "" : ", with severities: " + String.join(", ", rewritten))); } if (compareOcl != null) { System.out.print(compare(CompleteOclReader.read(reference), Path.of(compareOcl))); } } /** * The differences between the exported constraints and a deployed Complete OCL document, per invariant: * expression (an invariant with a severity: its tuple) or description changed, whitespace ignored, and * invariants missing on either side. A report for porting changes by hand. */ static String compare(java.util.List<ConstraintRecord> exported, Path deployedFile) throws IOException { java.util.Map<String, ConstraintRecord> deployed = new java.util.LinkedHashMap<>(); for (ConstraintRecord r : CompleteOclReader.read(deployedFile)) { deployed.put(r.toString(), r); } java.util.List<String> lines = new java.util.ArrayList<>(); java.util.Set<String> seen = new java.util.HashSet<>(); for (ConstraintRecord r : exported) { String id = r.toString(); seen.add(id); ConstraintRecord d = deployed.get(id); if (d == null) { lines.add(" + " + id + ": not in " + deployedFile.getFileName()); continue; } if (!normalised(r.expression).equals(normalised(d.expression))) { lines.add(" ~ " + id + ": expression differs"); } // in the tuple form the description is part of the expression, the header has none if (!r.description.isEmpty() && !d.description.isEmpty() && !normalised(r.description).equals(normalised(d.description))) { lines.add(" ~ " + id + ": description differs"); } } for (String id : deployed.keySet()) { if (!seen.contains(id)) { lines.add(" - " + id + ": only in " + deployedFile.getFileName()); } } return "Compared with " + deployedFile + ": " + (lines.isEmpty() ? "no differences\n" : lines.size() + " differences\n" + String.join("\n", lines) + "\n"); } private static String normalised(String s) { return s.trim().replaceAll("\\s+", " "); } /** Loading Loading
plugins/org.etsi.mts.tdl.constraints.helper/README.md +58 −4 Changes for plugins/org.etsi.mts.tdl.constraints.helper/README.md: 58 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -10,7 +10,8 @@ The implementation of the tool makes use of the Apache POI and related libraries ## Command line ConstraintExporter --format evl|ocl|ecore --in <file> [--ecore <file>] [--exclude Class::Name,...] ConstraintExporter --format evl|ocl|ecore --in <file> [--ecore <file>] [--severities <file>] [--interpreted-ocl <file>] [--compare-ocl <file>] [--exclude Class::Name,...] [--import <uri>,...] [--generated-package <name>] Classpath: the compiled classes, `lib/*` and the plug-ins of an Eclipse Modeling installation (for EMF). Loading @@ -20,14 +21,67 @@ Classpath: the compiled classes, `lib/*` and the plug-ins of an Eclipse Modeling - `--format ocl` (and `evl`) writes `resources/generated/tdl-generated-<title>.ocl`. - `--format ecore` additionally writes the invariants into the metamodel given with `--ecore`, in the form the OCLinEcore code generator compiles: per class the `constraints` list of the Ecore annotation, and per invariant the details `<Name>` (the expression) and `<Name>$message` (the description of the constraint followed by `self.toString()`). `error(...)` and `warning(...)` wrappers around an expression are dropped. The file is written with the EMF API, and per invariant one detail `<Name>` of the Pivot annotation holding the tuple-valued invariant of Eclipse OCL: Tuple { message : String = '<description of the constraint>' + self.toString(), severity : Integer = -1, status : Boolean = <expression> }.status The expression is copied as is, a line break before the closing brace is there because an expression may end in a `--` comment. The `severity` part is only written for invariants listed in the `--severities` file. The deprecated `<Name>$message` detail, which Eclipse OCL converts to this form on load, is removed when found. The file is written with the EMF API, so it keeps the formatting of the Ecore editor. The invariants that are no longer in the input are removed from the classes that are in the input. Operation bodies are not touched. A report lists what was added, updated, left unchanged and removed, the classes it did not find, and the invariants that use `allInstances()`. - `--severities` names a file of lines `Class::Name = error|warning|info` (`#` starts a comment) (`model/constraint-severities.properties` in the model plug-in), written as the `severity` part (-1, 1, 0). - `--interpreted-ocl` writes the Complete OCL export (from `--format ocl` or `ecore`) once more, with the invariants that have a configured severity in the same tuple form: what the deployed document of the interpreted validator should contain. Conventionally `model/interpreted/<document>.ocl` in the model plug-in. It is not loaded, acts as the reference for the review. - `--compare-ocl` compares the export (the `--interpreted-ocl` one if given) with a deployed Complete OCL document, per invariant and ignoring whitespace: expressions that differ, invariants missing on either side. Nothing is written. - `--exclude` keeps the named invariants out of the metamodel (they stay in the OCL output), for invariants the code generator cannot compile. Writing the invariants into a metamodel makes the EMF generator produce a validator for it. ## Workflow The specification is canonical - each part is exported on its own (one run per document): | Part | Document | Package options | Metamodel | Deployed OCL | |---|---|---|---|---| | 1 | `es_20311901…docx` | (default `tdl`) | `tdl.ecore` | `tdl-constraints.ocl` | | 4 | `es_20311904…docx` | `--generated-package structuredobjectives --import http://www.etsi.org/spec/TDL/1.3.1/structured` | `structured.ecore` | `tdl-structured-constraints.ocl` | | 7 | `es_20311907…docx` | `--generated-package extendedconfigurations --import http://www.etsi.org/spec/TDL/1.3.1/configurations` | `configurations.ecore` | `tdl-configurations-constraints.ocl` | Metamodel (compiled constraints, the default), from the plug-in folder: ConstraintExporter --format ecore --in <part 1 .docx> --ecore ../org.etsi.mts.tdl.model/model/tdl.ecore --severities ../org.etsi.mts.tdl.model/model/constraint-severities.properties Review of the deployed Complete OCL documents (interpreted constraints, being deprecated; they are maintained by hand): ConstraintExporter --format ocl --in <part 1 .docx> --severities ../org.etsi.mts.tdl.model/model/constraint-severities.properties --interpreted-ocl ../org.etsi.mts.tdl.model/model/interpreted/tdl-constraints.ocl --compare-ocl ../org.etsi.mts.tdl.model/model/tdl-constraints.ocl then correct the specification or the deployed document (porting from the export) and run again until the comparison reports no differences. Upgrade idea: apply the severities at runtime instead of at export, in the interpreted validator (`Validator` in the model plug-in: wrap the `DiagnosticChain` in `validatePivot` and set the severity of the diagnostics of configured constraints, recognised by their description) and, for the compiled invariants, through the OCL executor's severity hook or the Xtext diagnostic converter. One mechanism for both implementations, severities changeable without a re-export, and nothing to port by hand into the deployed documents; the cost is matching diagnostics back to constraints by message.
plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/ConstraintExporter.java +58 −4 Changes for plugins/org.etsi.mts.tdl.constraints.helper/src/org/etsi/mts/tdl/constraints/helper/ConstraintExporter.java: 58 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ public class ConstraintExporter { java.util.Set<String> excluded = new java.util.HashSet<>(); String severities = null; String interpretedOcl = null; String compareOcl = null; for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("--format") && args.length > i + 1) { Loading @@ -67,19 +68,25 @@ public class ConstraintExporter { severities = args[i + 1]; if (args[i].equalsIgnoreCase("--interpreted-ocl") && args.length > i + 1) interpretedOcl = args[i + 1]; if (args[i].equalsIgnoreCase("--compare-ocl") && args.length > i + 1) compareOcl = args[i + 1]; } ConstraintExporter exporter = new ConstraintExporter(); exporter.format = format; java.util.List<ConstraintRecord> constraints; // the Complete OCL text of the constraints: the input itself, or the export of the specification Path oclText; if (filename.endsWith(".ocl")) { // A Complete OCL document as input, only the ecore target makes sense. constraints = CompleteOclReader.read(Path.of(filename)); oclText = Path.of(filename); System.out.println("Read " + constraints.size() + " invariants from " + filename); } else { Document doc = DocumentHandler.loadDocument(new File(filename).getAbsolutePath()); String title = doc.getTitle().replaceAll(" ", "-"); String targetPath = "resources/generated/tdl-generated-" + title + (format == OutputFormat.EVL ? ".evl" : ".ocl"); oclText = format == OutputFormat.EVL ? null : Path.of(targetPath); exporter.exportConstraints(doc, generatedPackage, extraImports, targetPath); exporter.exportModel(doc, generatedPackage, extraImports, "resources/generated/tdl-generated-" + title + ".txt", true); constraints = exporter.getRecords(); Loading @@ -91,10 +98,10 @@ public class ConstraintExporter { exporter.exportGrammar(grammarDoc, generatedPackage, extraImports, "resources/generated/tdl-generated-" + title + "-grammar.txt", true); } } java.util.Map<String, Integer> severityOf = severities == null ? java.util.Collections.emptyMap() : readSeverities(Path.of(severities)); 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<>(); Loading @@ -113,13 +120,60 @@ public class ConstraintExporter { EcoreConstraintEmitter.Report report = EcoreConstraintEmitter.apply(Path.of(ecore), compilable); System.out.println("Wrote invariants into " + ecore); System.out.print(report); } // Review of a deployed document: the export (with the severities) is written next to it and compared with it. // Either the specification or the deployed document is corrected until they agree. if ((interpretedOcl != null || compareOcl != null) && oclText == null) throw new IllegalArgumentException("--interpreted-ocl and --compare-ocl need a Complete OCL export (--format ocl or ecore)"); Path reference = oclText; 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); java.util.List<String> rewritten = InterpretedOclWriter.write(oclText, Path.of(interpretedOcl), severityOf); reference = Path.of(interpretedOcl); System.out.println("Wrote the interpreted document " + interpretedOcl + (rewritten.isEmpty() ? "" : ", with severities: " + String.join(", ", rewritten))); } if (compareOcl != null) { System.out.print(compare(CompleteOclReader.read(reference), Path.of(compareOcl))); } } /** * The differences between the exported constraints and a deployed Complete OCL document, per invariant: * expression (an invariant with a severity: its tuple) or description changed, whitespace ignored, and * invariants missing on either side. A report for porting changes by hand. */ static String compare(java.util.List<ConstraintRecord> exported, Path deployedFile) throws IOException { java.util.Map<String, ConstraintRecord> deployed = new java.util.LinkedHashMap<>(); for (ConstraintRecord r : CompleteOclReader.read(deployedFile)) { deployed.put(r.toString(), r); } java.util.List<String> lines = new java.util.ArrayList<>(); java.util.Set<String> seen = new java.util.HashSet<>(); for (ConstraintRecord r : exported) { String id = r.toString(); seen.add(id); ConstraintRecord d = deployed.get(id); if (d == null) { lines.add(" + " + id + ": not in " + deployedFile.getFileName()); continue; } if (!normalised(r.expression).equals(normalised(d.expression))) { lines.add(" ~ " + id + ": expression differs"); } // in the tuple form the description is part of the expression, the header has none if (!r.description.isEmpty() && !d.description.isEmpty() && !normalised(r.description).equals(normalised(d.description))) { lines.add(" ~ " + id + ": description differs"); } } for (String id : deployed.keySet()) { if (!seen.contains(id)) { lines.add(" - " + id + ": only in " + deployedFile.getFileName()); } } return "Compared with " + deployedFile + ": " + (lines.isEmpty() ? "no differences\n" : lines.size() + " differences\n" + String.join("\n", lines) + "\n"); } private static String normalised(String s) { return s.trim().replaceAll("\\s+", " "); } /** Loading