Skip to content

Commit 53fc630

Browse files
Merge pull request #43 from OP-TED/TEDEFO-4806-tighter-type-checking
Tighter type checking (TEDEFO-4806)
2 parents 860c50e + c23a05e commit 53fc630

File tree

14 files changed

+418
-8
lines changed

14 files changed

+418
-8
lines changed

src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.util.Objects;
44
import com.fasterxml.jackson.databind.JsonNode;
5+
import eu.europa.ted.eforms.xpath.XPathInfo;
6+
import eu.europa.ted.eforms.xpath.XPathProcessor;
57

68
public abstract class SdkField implements Comparable<SdkField> {
79
private final String id;
@@ -10,6 +12,9 @@ public abstract class SdkField implements Comparable<SdkField> {
1012
private final String parentNodeId;
1113
private final String type;
1214
private final String codelistId;
15+
private final boolean repeatable;
16+
private SdkNode parentNode;
17+
private XPathInfo xpathInfo;
1318

1419
@SuppressWarnings("unused")
1520
private SdkField() {
@@ -18,12 +23,19 @@ private SdkField() {
1823

1924
protected SdkField(final String id, final String type, final String parentNodeId,
2025
final String xpathAbsolute, final String xpathRelative, final String codelistId) {
26+
this(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId, false);
27+
}
28+
29+
protected SdkField(final String id, final String type, final String parentNodeId,
30+
final String xpathAbsolute, final String xpathRelative, final String codelistId,
31+
final boolean repeatable) {
2132
this.id = id;
2233
this.parentNodeId = parentNodeId;
2334
this.xpathAbsolute = xpathAbsolute;
2435
this.xpathRelative = xpathRelative;
2536
this.type = type;
2637
this.codelistId = codelistId;
38+
this.repeatable = repeatable;
2739
}
2840

2941
protected SdkField(final JsonNode fieldNode) {
@@ -33,6 +45,7 @@ protected SdkField(final JsonNode fieldNode) {
3345
this.xpathRelative = fieldNode.get("xpathRelative").asText(null);
3446
this.type = fieldNode.get("type").asText(null);
3547
this.codelistId = extractCodelistId(fieldNode);
48+
this.repeatable = extractRepeatable(fieldNode);
3649
}
3750

3851
protected String extractCodelistId(final JsonNode fieldNode) {
@@ -49,6 +62,20 @@ protected String extractCodelistId(final JsonNode fieldNode) {
4962
return valueNode.get("id").asText(null);
5063
}
5164

65+
protected boolean extractRepeatable(final JsonNode fieldNode) {
66+
final JsonNode repeatableNode = fieldNode.get("repeatable");
67+
if (repeatableNode == null) {
68+
return false;
69+
}
70+
71+
final JsonNode valueNode = repeatableNode.get("value");
72+
if (valueNode == null) {
73+
return false;
74+
}
75+
76+
return valueNode.asBoolean(false);
77+
}
78+
5279
public String getId() {
5380
return id;
5481
}
@@ -73,6 +100,30 @@ public String getCodelistId() {
73100
return codelistId;
74101
}
75102

103+
public boolean isRepeatable() {
104+
return repeatable;
105+
}
106+
107+
public SdkNode getParentNode() {
108+
return parentNode;
109+
}
110+
111+
public void setParentNode(SdkNode parentNode) {
112+
this.parentNode = parentNode;
113+
}
114+
115+
/**
116+
* Returns parsed XPath information for this field.
117+
* Provides access to attribute info, path decomposition, and predicate checks.
118+
* Lazily initialized on first access.
119+
*/
120+
public XPathInfo getXpathInfo() {
121+
if (this.xpathInfo == null) {
122+
this.xpathInfo = XPathProcessor.parse(this.xpathAbsolute);
123+
}
124+
return this.xpathInfo;
125+
}
126+
76127
/**
77128
* Helps with hash maps collisions. Should be consistent with equals.
78129
*/

src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package eu.europa.ted.eforms.sdk.entity;
22

3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
36
import java.util.Objects;
47
import com.fasterxml.jackson.databind.JsonNode;
58

@@ -12,6 +15,8 @@ public abstract class SdkNode implements Comparable<SdkNode> {
1215
private final String xpathRelative;
1316
private final String parentId;
1417
private final boolean repeatable;
18+
private SdkNode parent;
19+
private List<String> cachedAncestry;
1520

1621
protected SdkNode(final String id, final String parentId, final String xpathAbsolute,
1722
final String xpathRelative, final boolean repeatable) {
@@ -51,9 +56,46 @@ public boolean isRepeatable() {
5156
return repeatable;
5257
}
5358

59+
public SdkNode getParent() {
60+
return parent;
61+
}
62+
63+
/**
64+
* Sets the parent node and invalidates the cached ancestry.
65+
* Should only be called during SDK initialization (two-pass loading).
66+
*
67+
* @param parent the parent node
68+
*/
69+
public void setParent(SdkNode parent) {
70+
this.parent = parent;
71+
this.cachedAncestry = null;
72+
}
73+
74+
/**
75+
* Returns the ancestry chain from this node to the root.
76+
* The list includes this node as the first element, followed by its parent,
77+
* grandparent, and so on up to the root node.
78+
*
79+
* The result is cached and recomputed only when the parent changes.
80+
*
81+
* @return unmodifiable list of node IDs ordered from child (this node) to root
82+
*/
83+
public List<String> getAncestry() {
84+
if (cachedAncestry == null) {
85+
List<String> ancestry = new ArrayList<>();
86+
SdkNode current = this;
87+
while (current != null) {
88+
ancestry.add(current.getId());
89+
current = current.getParent();
90+
}
91+
cachedAncestry = Collections.unmodifiableList(ancestry);
92+
}
93+
return cachedAncestry;
94+
}
95+
5496
@Override
5597
public int compareTo(SdkNode o) {
56-
return o.getId().compareTo(o.getId());
98+
return this.getId().compareTo(o.getId());
5799
}
58100

59101
@Override
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package eu.europa.ted.eforms.sdk.entity.v1;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import eu.europa.ted.eforms.sdk.component.SdkComponent;
6+
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
7+
import eu.europa.ted.eforms.sdk.entity.SdkCodelist;
8+
9+
/**
10+
* Representation of an SdkCodelist for usage in the symbols map.
11+
*
12+
* @author rouschr
13+
*/
14+
@SdkComponent(versions = {"1"}, componentType = SdkComponentType.CODELIST)
15+
public class SdkCodelistV1 extends SdkCodelist {
16+
17+
public SdkCodelistV1(final String codelistId, final String codelistVersion,
18+
final List<String> codes, final Optional<String> parentId) {
19+
super(codelistId, codelistVersion, codes, parentId);
20+
}
21+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package eu.europa.ted.eforms.sdk.entity.v1;
2+
3+
import java.util.Map;
4+
import com.fasterxml.jackson.annotation.JsonCreator;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import eu.europa.ted.eforms.sdk.component.SdkComponent;
8+
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
9+
import eu.europa.ted.eforms.sdk.entity.SdkField;
10+
11+
@SdkComponent(versions = {"1"}, componentType = SdkComponentType.FIELD)
12+
public class SdkFieldV1 extends SdkField {
13+
14+
public SdkFieldV1(final String id, final String type, final String parentNodeId,
15+
final String xpathAbsolute, final String xpathRelative, final String codelistId,
16+
final boolean repeatable) {
17+
super(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId, repeatable);
18+
}
19+
20+
public SdkFieldV1(final JsonNode field) {
21+
super(field);
22+
}
23+
24+
@JsonCreator
25+
public SdkFieldV1(
26+
@JsonProperty("id") final String id,
27+
@JsonProperty("type") final String type,
28+
@JsonProperty("parentNodeId") final String parentNodeId,
29+
@JsonProperty("xpathAbsolute") final String xpathAbsolute,
30+
@JsonProperty("xpathRelative") final String xpathRelative,
31+
@JsonProperty("codeList") final Map<String, Map<String, String>> codelist,
32+
@JsonProperty("repeatable") final Map<String, Object> repeatable) {
33+
this(id, type, parentNodeId, xpathAbsolute, xpathRelative, getCodelistId(codelist),
34+
getRepeatable(repeatable));
35+
}
36+
37+
protected static String getCodelistId(Map<String, Map<String, String>> codelist) {
38+
if (codelist == null) {
39+
return null;
40+
}
41+
42+
Map<String, String> value = codelist.get("value");
43+
if (value == null) {
44+
return null;
45+
}
46+
47+
return value.get("id");
48+
}
49+
50+
protected static boolean getRepeatable(Map<String, Object> repeatable) {
51+
if (repeatable == null) {
52+
return false;
53+
}
54+
55+
// If there are constraints, the field may repeat conditionally - treat as repeatable
56+
Object constraints = repeatable.get("constraints");
57+
if (constraints != null) {
58+
return true;
59+
}
60+
61+
// Otherwise check the default value
62+
Object value = repeatable.get("value");
63+
return Boolean.TRUE.equals(value);
64+
}
65+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package eu.europa.ted.eforms.sdk.entity.v1;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import eu.europa.ted.eforms.sdk.component.SdkComponent;
7+
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
8+
import eu.europa.ted.eforms.sdk.entity.SdkNode;
9+
10+
/**
11+
* A node is something like a section. Nodes can be parents of other nodes or parents of fields.
12+
*/
13+
@SdkComponent(versions = {"1"}, componentType = SdkComponentType.NODE)
14+
public class SdkNodeV1 extends SdkNode {
15+
16+
@JsonCreator
17+
public SdkNodeV1(
18+
@JsonProperty("id") String id,
19+
@JsonProperty("parentId") String parentId,
20+
@JsonProperty("xpathAbsolute") String xpathAbsolute,
21+
@JsonProperty("xpathRelative") String xpathRelative,
22+
@JsonProperty("repeatable") boolean repeatable) {
23+
super(id, parentId, xpathAbsolute, xpathRelative, repeatable);
24+
}
25+
26+
public SdkNodeV1(JsonNode node) {
27+
super(node);
28+
}
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package eu.europa.ted.eforms.sdk.entity.v1;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import eu.europa.ted.eforms.sdk.component.SdkComponent;
5+
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
6+
import eu.europa.ted.eforms.sdk.entity.SdkNoticeSubtype;
7+
8+
/**
9+
* Represents a notice subtype from the SDK's notice-types.json file.
10+
*/
11+
@SdkComponent(versions = {"1"}, componentType = SdkComponentType.NOTICE_TYPE)
12+
public class SdkNoticeSubtypeV1 extends SdkNoticeSubtype {
13+
14+
public SdkNoticeSubtypeV1(String subTypeId, String documentType, String type) {
15+
super(subTypeId, documentType, type);
16+
}
17+
18+
public SdkNoticeSubtypeV1(JsonNode json) {
19+
super(json);
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package eu.europa.ted.eforms.sdk.entity.v2;
2+
3+
import java.util.List;
4+
import java.util.Optional;
5+
import eu.europa.ted.eforms.sdk.component.SdkComponent;
6+
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
7+
import eu.europa.ted.eforms.sdk.entity.v1.SdkCodelistV1;
8+
9+
/**
10+
* Representation of an SdkCodelist for usage in the symbols map.
11+
*
12+
* @author rouschr
13+
*/
14+
@SdkComponent(versions = {"2"}, componentType = SdkComponentType.CODELIST)
15+
public class SdkCodelistV2 extends SdkCodelistV1 {
16+
17+
public SdkCodelistV2(final String codelistId, final String codelistVersion,
18+
final List<String> codes, final Optional<String> parentId) {
19+
super(codelistId, codelistVersion, codes, parentId);
20+
}
21+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package eu.europa.ted.eforms.sdk.entity.v2;
2+
3+
import java.util.Map;
4+
import com.fasterxml.jackson.annotation.JsonCreator;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import eu.europa.ted.eforms.sdk.component.SdkComponent;
8+
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
9+
import eu.europa.ted.eforms.sdk.entity.v1.SdkFieldV1;
10+
11+
@SdkComponent(versions = {"2"}, componentType = SdkComponentType.FIELD)
12+
public class SdkFieldV2 extends SdkFieldV1 {
13+
private final String alias;
14+
15+
public SdkFieldV2(String id, String type, String parentNodeId, String xpathAbsolute,
16+
String xpathRelative, String rootCodelistId, boolean repeatable, String alias) {
17+
super(id, type, parentNodeId, xpathAbsolute, xpathRelative, rootCodelistId, repeatable);
18+
this.alias = alias;
19+
}
20+
21+
public SdkFieldV2(JsonNode fieldNode) {
22+
super(fieldNode);
23+
this.alias = fieldNode.has("alias") ? fieldNode.get("alias").asText(null) : null;
24+
}
25+
26+
@JsonCreator
27+
public SdkFieldV2(
28+
@JsonProperty("id") final String id,
29+
@JsonProperty("type") final String type,
30+
@JsonProperty("parentNodeId") final String parentNodeId,
31+
@JsonProperty("xpathAbsolute") final String xpathAbsolute,
32+
@JsonProperty("xpathRelative") final String xpathRelative,
33+
@JsonProperty("codeList") final Map<String, Map<String, String>> codelist,
34+
@JsonProperty("repeatable") final Map<String, Object> repeatable,
35+
@JsonProperty("alias") final String alias) {
36+
this(id, type, parentNodeId, xpathAbsolute, xpathRelative, getCodelistId(codelist),
37+
getRepeatable(repeatable), alias);
38+
}
39+
40+
public String getAlias() {
41+
return alias;
42+
}
43+
}

0 commit comments

Comments
 (0)