Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/spec/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,24 @@ Covers:

Needs: impl, utest

#### Full Coverage Tag Format Allows Multiple Coverage
`dsn~import.full-coverage-tag-multiple-needed-coverage~1`

OFT imports full coverage tags with multiple need coverage ids:

full-tag-multiple-coverage-id =
"[" *WSP reference *WSP "->" *WSP requirement-id *WSP *("," *WSP requirement-id) *WSP "]"

Rationale:

An item can cover multiple IDs. This avoids creating multiple IDs for the same item solely to represent multiple coverage relations. It also reduces the number of IDs that related items must reference for complete coverage, improving readability and maintainability.

Covers:

* `req~import.full-coverage-tag-format~1`

Needs: impl, utest

#### Full Coverage Tag Format Allows Specifying a Revision
`dsn~import.full-coverage-tag-with-revision~1`

Expand Down
4 changes: 2 additions & 2 deletions doc/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ To avoid conflict with the formats actual contents, you embed these definitions
Tags have the following format:

```
[ <covered-artifact-type> -> <specification-object-id> ]
[ <covered-artifact-type> -> <list-of-specification-object-ids> ]
```

Spaces above were only added for readability. They are optional. In fact usually people prefer a more compact form.
Expand Down Expand Up @@ -737,7 +737,7 @@ Examples:
// [impl~validate-password~2->dsn~validate-authentication-request~1]
```

##### Forwarding Requirements
##### Needed Coverage

When using UML models as design document files like UML models it is useful to add needed coverage as well. To do this, you can use the following format:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import static java.util.Collections.emptyList;

import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.regex.Matcher;
Expand All @@ -23,18 +25,21 @@ class LongTagImportingLineConsumer extends AbstractRegexLineConsumer
private static final String OPTIONAL_WHITESPACE = "\\s*";
private static final String TAG_PREFIX = "\\[";
private static final String TAG_SUFFIX = "\\]";
private static final String NEEDS_COVERAGE = ">>" + OPTIONAL_WHITESPACE + "(\\p{Alpha}+(?:"
+ OPTIONAL_WHITESPACE
+ "," + OPTIONAL_WHITESPACE + "\\p{Alpha}+)*)";
private static final String COVERED_IDS = SpecificationItemId.ID_PATTERN + "(?:"
+ OPTIONAL_WHITESPACE + "," + OPTIONAL_WHITESPACE + SpecificationItemId.ID_PATTERN
+ ")*";
private static final String NEEDS_COVERAGE = ">>" + OPTIONAL_WHITESPACE
+ "(?<neededArtifactTypes>\\p{Alpha}+(?:" + OPTIONAL_WHITESPACE + ","
+ OPTIONAL_WHITESPACE + "\\p{Alpha}+)*)";
private static final String TAG_REGEX = TAG_PREFIX + OPTIONAL_WHITESPACE//
+ "(" + COVERING_ARTIFACT_TYPE_PATTERN + ")"
+ "(?<artifactType>" + COVERING_ARTIFACT_TYPE_PATTERN + ")"
+ "(?:" + SpecificationItemId.ARTIFACT_TYPE_SEPARATOR
// [impl->dsn~import.full-coverage-tag-with-name-and-revision~1]
+ "(" + SpecificationItemId.ITEM_NAME_PATTERN + ")?"
+ "(?<customName>" + SpecificationItemId.ITEM_NAME_PATTERN + ")?"
+ SpecificationItemId.REVISION_SEPARATOR
+ SpecificationItemId.ITEM_REVISION_PATTERN + ")?" //
+ "(?<revision>" + SpecificationItemId.ITEM_REVISION_PATTERN + "))?" //
+ OPTIONAL_WHITESPACE + "->" + OPTIONAL_WHITESPACE //
+ "(" + SpecificationItemId.ID_PATTERN + ")" //
+ "(?<coveredIds>" + COVERED_IDS + ")" //
+ OPTIONAL_WHITESPACE + "(?:" + NEEDS_COVERAGE + OPTIONAL_WHITESPACE + ")?" //
+ TAG_SUFFIX;

Expand All @@ -53,18 +58,27 @@ public void processMatch(final Matcher matcher, final int lineNumber, final int
{
this.listener.beginSpecificationItem();
this.listener.setLocation(this.file.getPath(), lineNumber);
final SpecificationItemId coveredId = SpecificationItemId.parseId(matcher.group(5));
final List<String> neededArtifactTypes = parseCommaSeparatedList(matcher.group(9));
final SpecificationItemId generatedId = createItemId(matcher, lineNumber, lineMatchCount, coveredId,
neededArtifactTypes);
logItem(lineNumber, coveredId, neededArtifactTypes, generatedId);
final List<SpecificationItemId> coveredIds = parseCoveredIds(matcher.group("coveredIds"));
final List<String> neededArtifactTypes = parseNeededArtifactTypes(matcher.group("neededArtifactTypes"));
final SpecificationItemId generatedId = createItemId(matcher, lineNumber, lineMatchCount,
coveredIds, neededArtifactTypes);
logItem(lineNumber, coveredIds, neededArtifactTypes, generatedId);
this.listener.setId(generatedId);
this.listener.addCoveredId(coveredId);
neededArtifactTypes.forEach(listener::addNeededArtifactType);
coveredIds.forEach(this.listener::addCoveredId);
neededArtifactTypes.forEach(this.listener::addNeededArtifactType);
this.listener.endSpecificationItem();
}

private static List<String> parseCommaSeparatedList(final String input)
private static List<SpecificationItemId> parseCoveredIds(final String input)
{
return Arrays.stream(input.split(","))
.map(String::trim)
.filter(Predicate.not(String::isEmpty))
.map(SpecificationItemId::parseId)
.toList();
}

private static List<String> parseNeededArtifactTypes(final String input)
{
if (input == null)
{
Expand All @@ -78,28 +92,28 @@ private static List<String> parseCommaSeparatedList(final String input)
}

private SpecificationItemId createItemId(final Matcher matcher, final int lineNumber, final int lineMatchCount,
final SpecificationItemId coveredId, final List<String> neededArtifactTypes)
final List<SpecificationItemId> coveredIds, final List<String> neededArtifactTypes)
{
final String artifactType = matcher.group(1);
final String customName = matcher.group(2);
final String revision = matcher.group(4);
final String artifactType = matcher.group("artifactType");
final String customName = matcher.group("customName");
final String revision = matcher.group("revision");
final String name = customName != null ? customName
: getItemName(lineNumber, lineMatchCount, coveredId, neededArtifactTypes);
: getItemName(lineNumber, lineMatchCount, coveredIds, neededArtifactTypes);
return SpecificationItemId.createId(artifactType, name, parseRevision(revision));
}

private void logItem(final int lineNumber, final SpecificationItemId coveredId,
private void logItem(final int lineNumber, final List<SpecificationItemId> coveredIds,
final List<String> neededArtifactTypes, final SpecificationItemId generatedId)
{
if (neededArtifactTypes.isEmpty())
{
LOG.finest(() -> "File " + this.file + ":" + lineNumber + ": found '" + generatedId
+ "' covering id '" + coveredId);
+ "' covering ids " + coveredIds);
}
else
{
LOG.finest(() -> "File " + this.file + ":" + lineNumber + ": found '" + generatedId
+ "' covering id '" + coveredId + "', needs artifact types "
+ "' covering ids " + coveredIds + ", needs artifact types "
+ neededArtifactTypes);
}
}
Expand All @@ -112,21 +126,32 @@ private static int parseRevision(final String revision)
}

// [impl->dsn~import.full-coverage-tag-with-needed-coverage-readable-names~1]
private String getItemName(final int lineNumber, final int lineMatchCount, final SpecificationItemId coveredId,
final List<String> neededArtifactTypes)
private String getItemName(final int lineNumber, final int lineMatchCount,
final List<SpecificationItemId> coveredIds, final List<String> neededArtifactTypes)
{
if (neededArtifactTypes.isEmpty())
{
return generateUniqueName(coveredId, lineNumber, lineMatchCount);
return generateUniqueName(coveredIds, lineNumber, lineMatchCount);
}
return coveredId.getName();
return joinCoveredIdNamesWithHyphen(coveredIds);
}

private String generateUniqueName(final SpecificationItemId coveredId, final int lineNumber,
private String generateUniqueName(final List<SpecificationItemId> coveredIds, final int lineNumber,
final int counter)
{
final String uniqueName = this.file.getPath() + lineNumber + counter + coveredId;
final String CoveredIdStringsJoinedWithHyphen = coveredIds.stream()
.map(SpecificationItemId::toString)
.collect(java.util.stream.Collectors.joining("-"));
final String uniqueName = this.file.getPath() + lineNumber + counter
+ CoveredIdStringsJoinedWithHyphen;
final String checksum = Long.toString(ChecksumCalculator.calculateCrc32(uniqueName));
return coveredId.getName() + "-" + checksum;
return joinCoveredIdNamesWithHyphen(coveredIds) + "-" + checksum;
}

private static String joinCoveredIdNamesWithHyphen(final List<SpecificationItemId> coveredIds)
{
return coveredIds.stream()
.map(SpecificationItemId::getName)
.collect(java.util.stream.Collectors.joining("-"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ static Stream<Arguments> tagImporterTests()
itemACoveringB("implA~name1-2943155783~0", "dsn~name1~2"),
itemACoveringB("implB~name2-1099447527~0", "dsn~name2~3"),
itemACoveringB("implC~name3-2846888323~0", "dsn~name3~4")),
parsedItem("[impl~combined~1->dsn~name1~2,dsn~name2~3]",
itemBuilder().id(SpecificationItemId.parseId("impl~combined~1"))
.addCoveredId(SpecificationItemId.parseId("dsn~name1~2"))
.addCoveredId(SpecificationItemId.parseId("dsn~name2~3"))),
parsedItem("[ impl~combined~1 -> dsn~name1~2 , dsn~name2~3 >> test ]",
itemBuilder().id(SpecificationItemId.parseId("impl~combined~1"))
.addCoveredId(SpecificationItemId.parseId("dsn~name1~2"))
.addCoveredId(SpecificationItemId.parseId("dsn~name2~3"))
.addNeedsArtifactType("test")),

parsedItems("[implA->dsn~name1~2" + "]" + UNIX_NEWLINE + "[implB->dsn~name2~3" + "]",
itemACoveringB("implA~name1-2943155783~0", "dsn~name1~2"),
Expand Down Expand Up @@ -163,6 +172,7 @@ static Stream<Arguments> tagImporterTests()
noItemDetected("[impl~missing-revision~->dsn~name2~2]"),
noItemDetected("[impl~illegal?char~1->dsn~name2~2]"),
noItemDetected("[impl~negative-revision~-1->dsn~name2~2]"),
noItemDetected("[impl~missing-covered-id~1->dsn~name2~2,]"),
noItemDetected("[impl~missing-forward~1->dsn~name2~2>>]"),
noItemDetected("[impl~trailing-comma~1->dsn~name2~2>>test,]"),
noItemDetected("[impl~duplicate-comma~1->dsn~name2~2>>test,,other]"),
Expand Down