Skip to content

Commit ab58282

Browse files
authored
#570: Fill item id location (#573)
1 parent b08320d commit ab58282

35 files changed

Lines changed: 852 additions & 150 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Needs: impl, utest
8585
### Code Style & Conventions
8686

8787
- **Clean Code**: Meaningful names, small functiKons, single responsibility.
88+
- **Type References**: Use plain class names with imports instead of fully qualified names such as `java.util.List`.
8889
- **Formatting**: Use the project's Eclipse formatter (`doc/itsallcode_formatter.xml`).
8990
- **Logging**: Use `java.util.logging`. Test config: `core/src/test/resources/logging.properties`.
9091

api/src/main/java/org/itsallcode/openfasttrace/api/core/SpecificationItem.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ public SpecificationItemId getId()
4848
return this.id.getId();
4949
}
5050

51+
/**
52+
* Get the declared ID together with its source occurrence.
53+
*
54+
* @return declared located ID
55+
*/
56+
public LocatedSpecificationItemId getLocatedId()
57+
{
58+
return this.id;
59+
}
60+
5161
/**
5262
* Get the artifact type of the specification item
5363
*
@@ -134,6 +144,16 @@ public List<SpecificationItemId> getCoveredIds()
134144
return this.coveredIds.stream().map(LocatedSpecificationItemId::getId).toList();
135145
}
136146

147+
/**
148+
* Get covered IDs together with their source occurrences.
149+
*
150+
* @return located covered IDs
151+
*/
152+
public List<LocatedSpecificationItemId> getLocatedCoveredIds()
153+
{
154+
return Collections.unmodifiableList(this.coveredIds);
155+
}
156+
137157
/**
138158
* Add a covered {@link SpecificationItemId} to the list of covered IDs.
139159
* <p>
@@ -167,6 +187,16 @@ public List<SpecificationItemId> getDependOnIds()
167187
return this.dependOnIds.stream().map(LocatedSpecificationItemId::getId).toList();
168188
}
169189

190+
/**
191+
* Get dependency IDs together with their source occurrences.
192+
*
193+
* @return located dependency IDs
194+
*/
195+
public List<LocatedSpecificationItemId> getLocatedDependOnIds()
196+
{
197+
return this.dependOnIds;
198+
}
199+
170200
/**
171201
* Get the list of artifact types this specification item need to be covered
172202
* in
@@ -274,6 +304,28 @@ public static Builder builder()
274304
return new Builder();
275305
}
276306

307+
/**
308+
* Create a builder pre-populated with this item's values.
309+
*
310+
* @return builder initialized from this item
311+
*/
312+
public Builder toBuilder()
313+
{
314+
final Builder builder = builder().id(this.id)
315+
.title(this.title)
316+
.description(this.description)
317+
.rationale(this.rationale)
318+
.comment(this.comment)
319+
.status(this.status)
320+
.location(this.location)
321+
.forwards(this.forwards);
322+
this.coveredIds.forEach(builder::addCoveredId);
323+
this.dependOnIds.forEach(builder::addDependOnId);
324+
this.needsArtifactTypes.forEach(builder::addNeedsArtifactType);
325+
this.tags.forEach(builder::addTag);
326+
return builder;
327+
}
328+
277329
/**
278330
* Builder for objects of type {@link SpecificationItem}
279331
*/
@@ -443,6 +495,20 @@ public Builder addCoveredId(final SpecificationItemId coveredId)
443495
return this.addCoveredId(locatedId(coveredId));
444496
}
445497

498+
/**
499+
* Replace the IDs of specification items covered by the item to build.
500+
*
501+
* @param coveredIds
502+
* the covered IDs
503+
* @return this builder instance
504+
*/
505+
public Builder coveredIds(final Collection<SpecificationItemId> coveredIds)
506+
{
507+
this.coveredIds.clear();
508+
coveredIds.forEach(this::addCoveredId);
509+
return this;
510+
}
511+
446512
/**
447513
* Add the ID of a specification item covered by the item to build
448514
*
@@ -486,6 +552,20 @@ public Builder addDependOnId(final LocatedSpecificationItemId dependOnId)
486552
return this;
487553
}
488554

555+
/**
556+
* Replace the IDs of specification items the item to build depends on.
557+
*
558+
* @param dependOnIds
559+
* the dependency IDs
560+
* @return this builder instance
561+
*/
562+
public Builder dependOnIds(final Collection<SpecificationItemId> dependOnIds)
563+
{
564+
this.dependOnIds.clear();
565+
dependOnIds.forEach(this::addDependOnId);
566+
return this;
567+
}
568+
489569
/**
490570
* Add the ID of a specification item the item to be build depends on
491571
*

api/src/main/java/org/itsallcode/openfasttrace/api/core/SpecificationItemId.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,28 +92,31 @@ public String getArtifactType()
9292
}
9393

9494
@Override
95-
public boolean equals(final Object o) {
96-
if (!(o instanceof final SpecificationItemId that)) {
95+
public boolean equals(final Object o)
96+
{
97+
if (!(o instanceof final SpecificationItemId that))
98+
{
9799
return false;
98100
}
99101
return revision == that.revision && Objects.equals(name, that.name)
100102
&& Objects.equals(artifactType, that.artifactType);
101103
}
102104

103105
@Override
104-
public int hashCode() {
106+
public int hashCode()
107+
{
105108
return Objects.hash(name, revision, artifactType);
106109
}
107110

108111
@Override
109112
public String toString()
110113
{
111-
return this.artifactType + ARTIFACT_TYPE_SEPARATOR + this.name + REVISION_SEPARATOR + this.revision;
114+
return this.artifactType + ARTIFACT_TYPE_SEPARATOR + this.name + REVISION_SEPARATOR + this.revision;
112115
}
113116

114117
/**
115118
* Get this item with a wildcard as revision.
116-
*
119+
*
117120
* @return a copy of this ID with a wildcard as revision.
118121
*/
119122
public SpecificationItemId toRevisionWildcard()
@@ -145,8 +148,7 @@ public static SpecificationItemId parseId(final String idText)
145148
* the revision
146149
* @return the specification item ID
147150
*/
148-
public static SpecificationItemId createId(final String artifactType, final String name,
149-
final int revision)
151+
public static SpecificationItemId createId(final String artifactType, final String name, final int revision)
150152
{
151153
return new SpecificationItemId.Builder().artifactType(artifactType).name(name)
152154
.revision(revision).build();

api/src/main/java/org/itsallcode/openfasttrace/api/importer/ImportEventListener.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public interface ImportEventListener
1313
/**
1414
* The importer found a new specification item. The
1515
* {@link SpecificationItemId} must be defined using
16-
* {@link #setId(SpecificationItemId)}.
16+
* {@link #setId(LocatedSpecificationItemId)}.
1717
*/
1818
void beginSpecificationItem();
1919

@@ -22,7 +22,9 @@ public interface ImportEventListener
2222
*
2323
* @param id
2424
* the ID of the new item
25+
* @deprecated Use {@link #setId(LocatedSpecificationItemId)} instead.
2526
*/
27+
@Deprecated(since = "4.9.0", forRemoval = true)
2628
void setId(final SpecificationItemId id);
2729

2830
/**
@@ -82,7 +84,9 @@ default void setId(final LocatedSpecificationItemId id)
8284
*
8385
* @param id
8486
* the ID of the item that is covered
87+
* @deprecated Use {@link #addCoveredId(LocatedSpecificationItemId)} instead.
8588
*/
89+
@Deprecated(since = "4.9.0", forRemoval = true)
8690
void addCoveredId(final SpecificationItemId id);
8791

8892
/**
@@ -101,7 +105,9 @@ default void addCoveredId(final LocatedSpecificationItemId id)
101105
*
102106
* @param id
103107
* the ID of the item depends on
108+
* @deprecated Use {@link #addDependsOnId(LocatedSpecificationItemId)} instead.
104109
*/
110+
@Deprecated(since = "4.9.0", forRemoval = true)
105111
void addDependsOnId(final SpecificationItemId id);
106112

107113
/**

api/src/main/java/org/itsallcode/openfasttrace/api/importer/SpecificationListBuilder.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public final class SpecificationListBuilder implements ImportEventListener
1515
private final FilterSettings filterSettings;
1616
private final List<SpecificationItem> items = new LinkedList<>();
1717
private SpecificationItem.Builder itemBuilder;
18-
private SpecificationItemId id;
18+
private LocatedSpecificationItemId id;
1919
private StringBuilder description = new StringBuilder();
2020
private StringBuilder rationale = new StringBuilder();
2121
private StringBuilder comment = new StringBuilder();
@@ -66,8 +66,16 @@ private void resetState()
6666
}
6767

6868
@Override
69+
@SuppressWarnings("removal") // Need to implement method from interface for backward compatibility
6970
public void setId(final SpecificationItemId id)
7071
{
72+
this.setId(LocatedSpecificationItemId.builder().id(id).build());
73+
}
74+
75+
@Override
76+
public void setId(final LocatedSpecificationItemId id)
77+
{
78+
// [impl->dsn~located-specification-item-id-storage~1]
7179
this.id = id;
7280
}
7381

@@ -78,10 +86,18 @@ public void setStatus(final ItemStatus status)
7886
}
7987

8088
@Override
89+
@SuppressWarnings("removal") // Need to implement method from interface for backward compatibility
8190
public void addCoveredId(final SpecificationItemId id)
91+
{
92+
this.addCoveredId(LocatedSpecificationItemId.builder().id(id).build());
93+
}
94+
95+
@Override
96+
public void addCoveredId(final LocatedSpecificationItemId id)
8297
{
8398
// [impl->dsn~filtering-by-artifact-types-during-import~1]
84-
if (isAcceptedArtifactType(id.getArtifactType()))
99+
// [impl->dsn~located-specification-item-id-storage~1]
100+
if (isAcceptedArtifactType(id.getId().getArtifactType()))
85101
{
86102
this.itemBuilder.addCoveredId(id);
87103
}
@@ -106,10 +122,18 @@ public void appendComment(final String fragment)
106122
}
107123

108124
@Override
125+
@SuppressWarnings("removal") // Need to implement method from interface for backward compatibility
109126
public void addDependsOnId(final SpecificationItemId id)
127+
{
128+
this.addDependsOnId(LocatedSpecificationItemId.builder().id(id).build());
129+
}
130+
131+
@Override
132+
public void addDependsOnId(final LocatedSpecificationItemId id)
110133
{
111134
// [impl->dsn~filtering-by-artifact-types-during-import~1]
112-
if (isAcceptedArtifactType(id.getArtifactType()))
135+
// [impl->dsn~located-specification-item-id-storage~1]
136+
if (isAcceptedArtifactType(id.getId().getArtifactType()))
113137
{
114138
this.itemBuilder.addDependOnId(id);
115139
}

api/src/test/java/org/itsallcode/openfasttrace/api/core/TestSpecificationItem.java

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.itsallcode.openfasttrace.api.core;
22

33
import static org.hamcrest.MatcherAssert.assertThat;
4-
import static org.hamcrest.Matchers.contains;
5-
import static org.hamcrest.Matchers.equalTo;
4+
import static org.hamcrest.Matchers.*;
65
import static org.junit.jupiter.api.Assertions.assertAll;
6+
import static org.junit.jupiter.api.Assertions.assertThrows;
7+
8+
import java.util.List;
79

810
import org.junit.jupiter.api.Test;
911

@@ -41,6 +43,77 @@ void testPreservesCompatibilityForUnlocatedIds()
4143
() -> assertThat(item.getDependOnIds(), contains(DEPEND_ON_ID)));
4244
}
4345

46+
// [utest->dsn~specification-item~3]
47+
@Test
48+
void testToBuilderPreservesAllValues()
49+
{
50+
final SpecificationItem item = SpecificationItem.builder().id(locatedId(ID)).title("Title")
51+
.description("Description").rationale("Rationale").comment("Comment").status(ItemStatus.DRAFT)
52+
.location(Location.create("file.md", 7)).addCoveredId(locatedId(COVERED_ID))
53+
.addDependOnId(locatedId(DEPEND_ON_ID)).addNeedsArtifactType("impl").addTag("important")
54+
.forwards(true).build();
55+
56+
assertThat(item.toBuilder().build(), equalTo(item));
57+
}
58+
59+
// [utest->dsn~specification-item~3]
60+
@Test
61+
void testToBuilderDoesNotModifyOriginalItem()
62+
{
63+
final SpecificationItem item = SpecificationItem.builder().id(ID).addCoveredId(COVERED_ID).build();
64+
65+
final SpecificationItem copy = item.toBuilder().addCoveredId(DEPEND_ON_ID).build();
66+
67+
assertAll(
68+
() -> assertThat(item.getCoveredIds(), contains(COVERED_ID)),
69+
() -> assertThat(copy, not(equalTo(item))),
70+
() -> assertThat(copy.getCoveredIds(), contains(COVERED_ID, DEPEND_ON_ID)));
71+
}
72+
73+
// [utest->dsn~specification-item~3]
74+
@Test
75+
void testBuilderReplacesCoveredIds()
76+
{
77+
final SpecificationItem item = SpecificationItem.builder().id(ID).addCoveredId(DEPEND_ON_ID)
78+
.coveredIds(List.of(COVERED_ID)).build();
79+
80+
assertThat(item.getCoveredIds(), contains(COVERED_ID));
81+
}
82+
83+
// [utest->dsn~specification-item~3]
84+
@Test
85+
void testBuilderReplacesDependencyIds()
86+
{
87+
final SpecificationItem item = SpecificationItem.builder().id(ID).addDependOnId(COVERED_ID)
88+
.dependOnIds(List.of(DEPEND_ON_ID)).build();
89+
90+
assertThat(item.getDependOnIds(), contains(DEPEND_ON_ID));
91+
}
92+
93+
// [utest->dsn~located-specification-item-id-storage~1]
94+
@Test
95+
void testLocatedCoveredIdsAreImmutable()
96+
{
97+
final SpecificationItem item = SpecificationItem.builder().id(locatedId(ID))
98+
.addCoveredId(locatedId(COVERED_ID)).addDependOnId(locatedId(DEPEND_ON_ID)).build();
99+
100+
final List<LocatedSpecificationItemId> immutableList = item.getLocatedCoveredIds();
101+
final LocatedSpecificationItemId locatedId = locatedId(ID);
102+
assertThrows(UnsupportedOperationException.class, () -> immutableList.add(locatedId));
103+
}
104+
105+
// [utest->dsn~located-specification-item-id-storage~1]
106+
@Test
107+
void testLocatedDependOnIdsAreImmutable()
108+
{
109+
final SpecificationItem item = SpecificationItem.builder().id(locatedId(ID))
110+
.addCoveredId(locatedId(COVERED_ID)).addDependOnId(locatedId(DEPEND_ON_ID)).build();
111+
112+
final List<LocatedSpecificationItemId> immutableList = item.getLocatedDependOnIds();
113+
final LocatedSpecificationItemId locatedId = locatedId(ID);
114+
assertThrows(UnsupportedOperationException.class, () -> immutableList.add(locatedId));
115+
}
116+
44117
private static LocatedSpecificationItemId locatedId(final SpecificationItemId id)
45118
{
46119
return LocatedSpecificationItemId.builder().id(id).range(

0 commit comments

Comments
 (0)