Skip to content

Commit cf534f2

Browse files
committed
Fix SpecRelation type semantics and parse relation attributes
The inherited type field carries a content category (REQ/SUB-REQ/ HEADLINE/TEXT) but was overwritten with the raw SPEC-RELATION-TYPE-REF id, so the inherited category methods returned nonsense for relations and the relation's own attribute values were never read. - type is now ReqIFConst.UNDEFINED for relations (no content category); the structural kind stays available via getSpecType() - Add getRelationTypeRef() (raw ref) and getRelationTypeName() (resolved LONG-NAME) - Parse the relation's VALUES block: extract the attribute-value reading from the SpecObject constructor into a reusable readAttributeValues(), used by both classes - Override isReq/isSubReq/isHeadline/isText to false; isText() formerly returned true for every relation - Navigate SOURCE/TARGET/TYPE via XmlUtils (namespace- and whitespace-independent) - Document in README and FEHLERANALYSE.md - Tests: SpecRelationTest BREAKING: getType() returns "UNDEFINED" for relations instead of the relation type reference id; use getRelationTypeRef() instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mat2d7AJkouKhXWUYzHxs
1 parent 54a52f3 commit cf534f2

6 files changed

Lines changed: 212 additions & 19 deletions

File tree

FEHLERANALYSE.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ die bei jedem Push in der GitHub-Actions-Pipeline (`.github/workflows/ci.yml`, `
1515
| Crash-/Robustheitsfehler (Abschnitt 3 + 4.1) | "Fix crash bugs and robustness issues" | `RobustnessTest` |
1616
| LONG-NAME-Heuristik (4.2) → konfigurierbare Strategie | "Make spec object type classification pluggable" | `TypeClassifierTest` |
1717
| Attributbasierte Klassifizierung (Implementor-Guide-Profil) | "Add attribute-based ReqIF Implementation Guide classifier" | `ImplementationGuideClassifierTest` |
18+
| `SpecRelation`-Typsemantik + Relationsattribute (4.3) | "Fix SpecRelation type semantics and parse relation attributes" | `SpecRelationTest` |
1819

1920
Zur Heuristik (4.2): Die Klassifizierung ist jetzt eine Strategie (`TypeClassifier`),
2021
die das fertig geparste `SpecObject` (inkl. Attributwerte) erhält.
@@ -30,9 +31,16 @@ Hinweis: `REQ`/`SUB-REQ`/`HEADLINE`/`TEXT` sind **keine** offiziellen ReqIF-Type
3031
sondern parserinterne Inhaltskategorien. Der OMG-Standard definiert nur strukturelle
3132
Typen (`SPEC-OBJECT-TYPE` usw.) mit frei vergebenen Namen/Attributen.
3233

34+
Zu `SpecRelation` (4.3): Das geerbte Feld `type` trägt eine Inhaltskategorie und wurde
35+
mit der Relationstyp-Referenz überschrieben. Es enthält jetzt `UNDEFINED` (eine Relation
36+
hat keine Inhaltskategorie); die Referenz liegt in `getRelationTypeRef()`, der aufgelöste
37+
Name in `getRelationTypeName()`, die strukturelle Information weiterhin in `getSpecType()`
38+
(`SPEC-RELATION-TYPE`). Die Attributwerte der Relation werden jetzt geparst.
39+
**Breaking Change:** `getType()` liefert für Relationen `"UNDEFINED"` statt der Referenz-ID.
40+
3341
Bewusst (noch) nicht angefasst, da Verhaltensänderungen für bestehende Nutzer:
34-
die `type`-Semantik von `SpecRelation` (4.3),
35-
HTML-Escaping/Attribut-Erhalt in `toString()` (4.8) sowie die kosmetischen Punkte aus Abschnitt 5
42+
Tabellen-/Listen-Deconstruction (4.7), HTML-Escaping/Attribut-Erhalt in `toString()` (4.8)
43+
sowie die kosmetischen Punkte aus Abschnitt 5
3644
(bis auf den entfernten `javax.xml.crypto.Data`-Import).
3745

3846
Die ursprüngliche Analyse folgt unverändert. Datei- und Zeilenangaben beziehen sich auf den

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,22 @@ TypeClassifier classifier = new TypeClassifier() {
101101
ReqIF reqif = new ReqIF("spec.reqif", classifier); // .reqif
102102
ReqIFz reqifz = new ReqIFz("archive.reqifz", classifier); // .reqifz
103103
```
104+
105+
# Spec relations
106+
`SpecRelation` extends `SpecObject`, but a relation has no *content*
107+
category, so the inherited `getType()` returns `ReqIFConst.UNDEFINED`.
108+
The relation's own type and its endpoints are exposed separately:
109+
110+
```java
111+
SpecRelation rel = reqif.getReqIFCoreContent().getSpecRelation("sr-1");
112+
113+
rel.getSourceObjID(); // "so-1"
114+
rel.getTargetObjID(); // "so-2"
115+
rel.getRelationTypeRef(); // "st-rel" (SPEC-RELATION-TYPE-REF)
116+
rel.getRelationTypeName(); // "satisfies" (resolved LONG-NAME)
117+
rel.getSpecType(); // "SPEC-RELATION-TYPE"
118+
rel.getAttribute("LinkComment"); // relation attribute values are parsed
119+
```
120+
121+
`isReq()`, `isSubReq()`, `isHeadline()` and `isText()` all return `false`
122+
for relations.

src/main/java/de/uni_stuttgart/ils/reqif4j/specification/SpecObject.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ public SpecObject(Node specObject, SpecType specType, TypeClassifier typeClassif
9292
this.specType = specType;
9393
this.typeClassifier = typeClassifier == null ? TypeClassifier.defaultClassifier() : typeClassifier;
9494

95+
readAttributeValues(specObject, specType);
96+
97+
// Classify only after all attribute values are available, so an
98+
// attribute-based classifier (e.g. the ReqIF Implementation Guide
99+
// profile) can inspect them.
100+
this.type = this.typeClassifier.classify(this);
101+
}
102+
103+
/**
104+
* Reads all attribute values of the given node and fills in default values
105+
* for attribute definitions that carry no explicit value. Shared by
106+
* {@link SpecObject} and {@link SpecRelation}.
107+
*/
108+
protected void readAttributeValues(Node specObject, SpecType specType) {
109+
95110
if( ((Element)specObject).getElementsByTagName(ReqIFConst.VALUES).getLength() > 0
96111
&& ((Element)specObject).getElementsByTagName(ReqIFConst.VALUES).item(0).hasChildNodes() ) {
97112

@@ -215,11 +230,6 @@ public SpecObject(Node specObject, SpecType specType, TypeClassifier typeClassif
215230
}
216231
}
217232
}
218-
219-
// Classify only after all attribute values are available, so an
220-
// attribute-based classifier (e.g. the ReqIF Implementation Guide
221-
// profile) can inspect them.
222-
this.type = this.typeClassifier.classify(this);
223233
}
224234

225235
}
Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,98 @@
11
package de.uni_stuttgart.ils.reqif4j.specification;
22

33
import de.uni_stuttgart.ils.reqif4j.reqif.ReqIFConst;
4+
import de.uni_stuttgart.ils.reqif4j.util.XmlUtils;
45
import org.w3c.dom.Element;
56
import org.w3c.dom.Node;
67

7-
public class SpecRelation extends SpecObject{
8+
/**
9+
* A relation (link) between two spec objects.
10+
*
11+
* The inherited {@code type} field carries a <em>content</em> category
12+
* (REQ/SUB-REQ/HEADLINE/TEXT) which does not apply to a relation, so it is set
13+
* to {@link ReqIFConst#UNDEFINED}. The structural information "this is a
14+
* relation" is available via {@link #getSpecType()} ("SPEC-RELATION-TYPE"),
15+
* the relation's own type via {@link #getRelationTypeRef()} and
16+
* {@link #getRelationTypeName()}.
17+
*/
18+
public class SpecRelation extends SpecObject {
19+
820
private String sourceObjID;
921
private String targetObjID;
22+
private String relationTypeRef;
1023

1124
public SpecRelation(Node specRelation, SpecType specType) {
1225
super(specRelation);
1326
this.specType = specType;
14-
Element eSpecRelation = (Element)specRelation;
27+
1528
// Get target and source node
16-
Element sourceNode = (Element) eSpecRelation.getElementsByTagName(ReqIFConst.SOURCE).item(0);
17-
Element targetNode = (Element) eSpecRelation.getElementsByTagName(ReqIFConst.TARGET).item(0);
18-
sourceObjID = sourceNode.getElementsByTagName(ReqIFConst.SPEC_OBJECT_REF).item(0).getTextContent();
19-
targetObjID = targetNode.getElementsByTagName(ReqIFConst.SPEC_OBJECT_REF).item(0).getTextContent();
20-
// Get relationship type
21-
Element typeNode = (Element) eSpecRelation.getElementsByTagName(ReqIFConst.TYPE).item(0);
22-
this.type = typeNode.getElementsByTagName(ReqIFConst.SPEC_RELATION_TYPE_REF).item(0).getTextContent();
29+
Element sourceNode = XmlUtils.firstChildElementByLocalName(specRelation, ReqIFConst.SOURCE);
30+
Element targetNode = XmlUtils.firstChildElementByLocalName(specRelation, ReqIFConst.TARGET);
31+
this.sourceObjID = specObjectRef(sourceNode);
32+
this.targetObjID = specObjectRef(targetNode);
33+
34+
// Get relationship type reference
35+
Element typeNode = XmlUtils.firstChildElementByLocalName(specRelation, ReqIFConst.TYPE);
36+
Element typeRef = XmlUtils.firstDescendantByLocalName(typeNode, ReqIFConst.SPEC_RELATION_TYPE_REF);
37+
this.relationTypeRef = typeRef == null ? null : typeRef.getTextContent().trim();
38+
39+
// A relation has no content category; the relation type lives in
40+
// relationTypeRef, not in the inherited type field.
41+
this.type = ReqIFConst.UNDEFINED;
42+
43+
if (specType != null) {
44+
readAttributeValues(specRelation, specType);
45+
}
2346
}
2447

2548
public String getSourceObjID() {
26-
return sourceObjID;
49+
return this.sourceObjID;
2750
}
2851

2952
public String getTargetObjID() {
30-
return targetObjID;
53+
return this.targetObjID;
54+
}
55+
56+
/**
57+
* @return the IDENTIFIER of the SPEC-RELATION-TYPE this relation refers to
58+
* (SPEC-RELATION-TYPE-REF), or null if the document declares none
59+
*/
60+
public String getRelationTypeRef() {
61+
return this.relationTypeRef;
62+
}
63+
64+
/**
65+
* @return the LONG-NAME of the relation type (e.g. "satisfies"), or null if
66+
* the reference could not be resolved
67+
*/
68+
public String getRelationTypeName() {
69+
return this.specType == null ? null : this.specType.getName();
70+
}
71+
72+
// A relation is none of the content categories.
73+
74+
@Override
75+
public boolean isReq() {
76+
return false;
77+
}
78+
79+
@Override
80+
public boolean isSubReq() {
81+
return false;
82+
}
83+
84+
@Override
85+
public boolean isHeadline() {
86+
return false;
87+
}
88+
89+
@Override
90+
public boolean isText() {
91+
return false;
92+
}
93+
94+
private static String specObjectRef(Element sourceOrTarget) {
95+
Element ref = XmlUtils.firstDescendantByLocalName(sourceOrTarget, ReqIFConst.SPEC_OBJECT_REF);
96+
return ref == null ? null : ref.getTextContent().trim();
3197
}
3298
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package de.uni_stuttgart.ils.reqif4j;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
7+
import java.nio.file.Path;
8+
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.io.TempDir;
12+
13+
import de.uni_stuttgart.ils.reqif4j.reqif.ReqIF;
14+
import de.uni_stuttgart.ils.reqif4j.reqif.ReqIFConst;
15+
import de.uni_stuttgart.ils.reqif4j.specification.SpecRelation;
16+
17+
/**
18+
* Bug: SpecRelation overwrote the inherited {@code type} field — which carries
19+
* a content category (REQ/HEADLINE/TEXT) — with the raw
20+
* SPEC-RELATION-TYPE-REF id, so the inherited category methods returned
21+
* nonsense. The relation's own attribute values were not parsed at all.
22+
*/
23+
class SpecRelationTest {
24+
25+
private SpecRelation relation;
26+
27+
@BeforeEach
28+
void parseFixture(@TempDir Path tempDir) throws Exception {
29+
ReqIF reqif = new ReqIF(TestFixtures.writeDefaultFixture(tempDir).toString());
30+
relation = reqif.getReqIFCoreContent().getSpecRelation("sr-1");
31+
assertNotNull(relation, "the fixture's spec relation must be parsed");
32+
}
33+
34+
@Test
35+
void sourceAndTargetAreParsed() {
36+
// regression guard for the original DOORS relationship reader
37+
assertEquals("so-1", relation.getSourceObjID());
38+
assertEquals("so-2", relation.getTargetObjID());
39+
}
40+
41+
@Test
42+
void inheritedTypeIsUndefinedInsteadOfTheRelationTypeRef() {
43+
assertEquals(ReqIFConst.UNDEFINED, relation.getType(),
44+
"a relation has no content category; the type ref must not leak into type");
45+
}
46+
47+
@Test
48+
void relationTypeIsExposedSeparately() {
49+
assertEquals("st-rel", relation.getRelationTypeRef());
50+
assertEquals("satisfies", relation.getRelationTypeName());
51+
}
52+
53+
@Test
54+
void structuralKindIsStillAvailable() {
55+
assertEquals(ReqIFConst.SPEC_RELATION_TYPE, relation.getSpecType(),
56+
"the information 'this is a relation' lives in getSpecType()");
57+
}
58+
59+
@Test
60+
void contentCategoryMethodsAreAllFalse() {
61+
assertFalse(relation.isReq());
62+
assertFalse(relation.isSubReq());
63+
assertFalse(relation.isHeadline());
64+
assertFalse(relation.isText(), "a relation is not text (formerly reported true)");
65+
}
66+
67+
@Test
68+
void relationAttributeValuesAreParsed() {
69+
assertEquals("derived during review", relation.getAttribute("LinkComment"),
70+
"attribute values of a relation were formerly ignored");
71+
}
72+
}

src/test/java/de/uni_stuttgart/ils/reqif4j/TestFixtures.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ private TestFixtures() {
8282
</ATTRIBUTE-DEFINITION-XHTML>
8383
</SPEC-ATTRIBUTES>
8484
</SPEC-OBJECT-TYPE>
85+
<SPEC-RELATION-TYPE IDENTIFIER="st-rel" LONG-NAME="satisfies">
86+
<SPEC-ATTRIBUTES>
87+
<ATTRIBUTE-DEFINITION-STRING IDENTIFIER="ad-linkcomment" LONG-NAME="LinkComment">
88+
<TYPE><DATATYPE-DEFINITION-STRING-REF>dt-string</DATATYPE-DEFINITION-STRING-REF></TYPE>
89+
</ATTRIBUTE-DEFINITION-STRING>
90+
</SPEC-ATTRIBUTES>
91+
</SPEC-RELATION-TYPE>
8592
<SPECIFICATION-TYPE IDENTIFIER="st-spec" LONG-NAME="Spec Type">
8693
<SPEC-ATTRIBUTES>
8794
<ATTRIBUTE-DEFINITION-DATE IDENTIFIER="ad-review" LONG-NAME="ReviewDate">
@@ -124,7 +131,18 @@ private TestFixtures() {
124131
</VALUES>
125132
</SPEC-OBJECT>
126133
</SPEC-OBJECTS>
127-
<SPEC-RELATIONS/>
134+
<SPEC-RELATIONS>
135+
<SPEC-RELATION IDENTIFIER="sr-1">
136+
<VALUES>
137+
<ATTRIBUTE-VALUE-STRING THE-VALUE="derived during review">
138+
<DEFINITION><ATTRIBUTE-DEFINITION-STRING-REF>ad-linkcomment</ATTRIBUTE-DEFINITION-STRING-REF></DEFINITION>
139+
</ATTRIBUTE-VALUE-STRING>
140+
</VALUES>
141+
<TYPE><SPEC-RELATION-TYPE-REF>st-rel</SPEC-RELATION-TYPE-REF></TYPE>
142+
<SOURCE><SPEC-OBJECT-REF>so-1</SPEC-OBJECT-REF></SOURCE>
143+
<TARGET><SPEC-OBJECT-REF>so-2</SPEC-OBJECT-REF></TARGET>
144+
</SPEC-RELATION>
145+
</SPEC-RELATIONS>
128146
<SPECIFICATIONS>
129147
<SPECIFICATION IDENTIFIER="spec-1" LONG-NAME="Main Spec">
130148
<TYPE><SPECIFICATION-TYPE-REF>st-spec</SPECIFICATION-TYPE-REF></TYPE>

0 commit comments

Comments
 (0)