Anpassung an DQFL Specification V 1.1
This commit is contained in:
12
pom.xml
12
pom.xml
@@ -1,6 +1,6 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="https://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>de.dogfire.dqfl</groupId>
|
||||
@@ -32,18 +32,18 @@
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://dein-gitea-server/api/packages/pat201290/maven</url>
|
||||
<url>https://gitea.dogfire.de/api/packages/pat201290/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://dein-gitea-server/api/packages/pat201290/maven</url>
|
||||
<url>https://gitea.dogfire.de/api/packages/pat201290/maven</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>gitea</id>
|
||||
<url>https://dein-gitea-server/api/packages/pat201290/maven</url>
|
||||
<url>https://gitea.dogfire.de/api/packages/pat201290/maven</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package de.dogfire.dqfl.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -14,6 +13,7 @@ public final class DqflCategory
|
||||
private final String alias;
|
||||
private final String title;
|
||||
private final String usePool;
|
||||
private final String useCategory;
|
||||
private final LayoutType layout;
|
||||
private final GridSize size;
|
||||
private final List<DqflPhase> phases;
|
||||
@@ -23,6 +23,7 @@ public final class DqflCategory
|
||||
this.alias = Objects.requireNonNull(builder.alias, "alias must not be null");
|
||||
this.title = builder.title;
|
||||
this.usePool = builder.usePool;
|
||||
this.useCategory = builder.useCategory;
|
||||
this.layout = builder.layout;
|
||||
this.size = builder.size;
|
||||
this.phases = Collections.unmodifiableList(new ArrayList<>(builder.phases));
|
||||
@@ -44,6 +45,12 @@ public final class DqflCategory
|
||||
return this.usePool;
|
||||
}
|
||||
|
||||
/** Kann null sein — USE_CATEGORY ist optional (V1_1). */
|
||||
public String getUseCategory()
|
||||
{
|
||||
return this.useCategory;
|
||||
}
|
||||
|
||||
public LayoutType getLayout()
|
||||
{
|
||||
return this.layout;
|
||||
@@ -66,6 +73,7 @@ public final class DqflCategory
|
||||
return "CATEGORY " + this.alias
|
||||
+ " [title=" + this.title
|
||||
+ ", pool=" + this.usePool
|
||||
+ ", category=" + this.useCategory
|
||||
+ ", phases=" + this.phases.size() + "]";
|
||||
}
|
||||
|
||||
@@ -83,6 +91,7 @@ public final class DqflCategory
|
||||
private final String alias;
|
||||
private String title;
|
||||
private String usePool;
|
||||
private String useCategory;
|
||||
private LayoutType layout;
|
||||
private GridSize size;
|
||||
private final List<DqflPhase> phases = new ArrayList<>();
|
||||
@@ -104,6 +113,12 @@ public final class DqflCategory
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder useCategory(final String useCategory)
|
||||
{
|
||||
this.useCategory = useCategory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder layout(final LayoutType layout)
|
||||
{
|
||||
this.layout = layout;
|
||||
|
||||
@@ -10,7 +10,7 @@ import de.dogfire.dqfl.error.DqflParseException;
|
||||
import de.dogfire.dqfl.model.*;
|
||||
|
||||
/**
|
||||
* Parser für DQFL V1 Dokumente.
|
||||
* Parser für DQFL V1/V1_1 Dokumente.
|
||||
* Liest ein einrückungssensitives Textformat und erzeugt einen {@link DqflDocument} AST.
|
||||
*/
|
||||
public final class DqflParser
|
||||
@@ -330,6 +330,10 @@ public final class DqflParser
|
||||
catBuilder.usePool(child.value);
|
||||
this.cursor++;
|
||||
}
|
||||
case "USE_CATEGORY" -> {
|
||||
catBuilder.useCategory(this.unquote(child.value));
|
||||
this.cursor++;
|
||||
}
|
||||
case "LAYOUT" -> {
|
||||
final LayoutType lt = LayoutType.parse(child.value);
|
||||
if(lt == null)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package de.dogfire.dqfl.validator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -12,8 +11,8 @@ import de.dogfire.dqfl.error.DqflValidationResult;
|
||||
import de.dogfire.dqfl.model.*;
|
||||
|
||||
/**
|
||||
* Validator für DQFL V1 Dokumente.
|
||||
* Prüft alle Fachregeln aus Kapitel 10 der Spezifikation.
|
||||
* Validator für DQFL V1/V1_1 Dokumente.
|
||||
* Prüft alle Fachregeln aus der Spezifikation.
|
||||
*/
|
||||
public final class DqflValidator
|
||||
{
|
||||
@@ -40,25 +39,18 @@ public final class DqflValidator
|
||||
errors.add(DqflError.error("FLOW is required"));
|
||||
}
|
||||
|
||||
// 3. SOURCE-Aliase müssen eindeutig sein (bereits durch Map im Parser sichergestellt,
|
||||
// aber wir prüfen trotzdem ob Sources vorhanden sind wenn Pools sie referenzieren)
|
||||
|
||||
// 4. POOL-Validierung
|
||||
// 3. POOL-Validierung
|
||||
this.validatePools(document, errors);
|
||||
|
||||
// 5. ROUND-Validierung
|
||||
// 4. ROUND-Validierung
|
||||
this.validateRounds(document, errors);
|
||||
|
||||
// 6. Alias-Eindeutigkeit über Objektarten
|
||||
// 5. Alias-Eindeutigkeit über Objektarten
|
||||
this.validateAliasUniqueness(document, errors);
|
||||
|
||||
return new DqflValidationResult(errors);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Pool-Validierung
|
||||
// ================================================================
|
||||
|
||||
private void validatePools(final DqflDocument document, final List<DqflError> errors)
|
||||
{
|
||||
for(final DqflPool pool : document.getPools().values())
|
||||
@@ -82,10 +74,6 @@ public final class DqflValidator
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Round-Validierung
|
||||
// ================================================================
|
||||
|
||||
private void validateRounds(final DqflDocument document, final List<DqflError> errors)
|
||||
{
|
||||
if(document.getRounds().isEmpty())
|
||||
@@ -119,10 +107,6 @@ public final class DqflValidator
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Category-Validierung
|
||||
// ================================================================
|
||||
|
||||
private void validateCategory(
|
||||
final DqflCategory category,
|
||||
final DqflDocument document,
|
||||
@@ -153,6 +137,26 @@ public final class DqflValidator
|
||||
}
|
||||
}
|
||||
|
||||
// USE_CATEGORY Validierung (V1_1)
|
||||
if(category.getUseCategory() != null)
|
||||
{
|
||||
// USE_CATEGORY ohne USE_POOL ist ein Syntaxfehler
|
||||
if(category.getUsePool() == null || category.getUsePool().isBlank())
|
||||
{
|
||||
errors.add(DqflError.error(
|
||||
"CATEGORY '" + category.getAlias()
|
||||
+ "' has USE_CATEGORY without USE_POOL"));
|
||||
}
|
||||
|
||||
// Wert darf nicht leer sein
|
||||
if(category.getUseCategory().isBlank())
|
||||
{
|
||||
errors.add(DqflError.error(
|
||||
"CATEGORY '" + category.getAlias()
|
||||
+ "' USE_CATEGORY must not be empty"));
|
||||
}
|
||||
}
|
||||
|
||||
// SIZE nur gültig wenn LAYOUT vorhanden
|
||||
if(category.getSize() != null && category.getLayout() == null)
|
||||
{
|
||||
@@ -182,10 +186,6 @@ public final class DqflValidator
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Phase-Validierung
|
||||
// ================================================================
|
||||
|
||||
private void validatePhase(
|
||||
final DqflPhase phase,
|
||||
final String categoryAlias,
|
||||
@@ -236,19 +236,10 @@ public final class DqflValidator
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Alias-Eindeutigkeit (über Objektarten)
|
||||
// ================================================================
|
||||
|
||||
private void validateAliasUniqueness(
|
||||
final DqflDocument document,
|
||||
final List<DqflError> errors)
|
||||
{
|
||||
// SOURCE-Aliase eindeutig (Map-Struktur prüft bereits implizit,
|
||||
// aber bei Duplikaten im Dokument wurde der erste überschrieben)
|
||||
// POOL-Aliase eindeutig (gleiche Map-Logik)
|
||||
|
||||
// Wir prüfen explizit ob Pool-Aliase und Source-Aliase kollidieren (optional, sauber)
|
||||
final Set<String> sourceAliases = document.getSources().keySet();
|
||||
final Set<String> poolAliases = document.getPools().keySet();
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package de.dogfire.dqfl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -14,7 +13,7 @@ import de.dogfire.dqfl.error.DqflValidationResult;
|
||||
import de.dogfire.dqfl.model.*;
|
||||
|
||||
/**
|
||||
* Tests für die DQFL V1 Library: Parser + Validator.
|
||||
* Tests für die DQFL V1/V1_1 Library: Parser + Validator.
|
||||
*/
|
||||
class DqflTest
|
||||
{
|
||||
@@ -75,6 +74,7 @@ class DqflTest
|
||||
assertEquals("C1", c1.getAlias());
|
||||
assertEquals("Geschichte", c1.getTitle());
|
||||
assertEquals("HISTORY", c1.getUsePool());
|
||||
assertNull(c1.getUseCategory());
|
||||
assertEquals(LayoutType.GRID_2D, c1.getLayout());
|
||||
assertEquals(new GridSize(6, 6), c1.getSize());
|
||||
assertEquals(2, c1.getPhases().size());
|
||||
@@ -322,6 +322,143 @@ class DqflTest
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// V1_1 USE_CATEGORY Tests
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("V1_1 USE_CATEGORY")
|
||||
class UseCategoryTests
|
||||
{
|
||||
@Test
|
||||
@DisplayName("Parst USE_CATEGORY korrekt")
|
||||
void parseUseCategory() throws Exception
|
||||
{
|
||||
final String input = """
|
||||
DQFL_VERSION V1_1
|
||||
FLOW "Test"
|
||||
SOURCE SRC1
|
||||
TYPE REST
|
||||
CONNECTION CONN1
|
||||
POOL GEO
|
||||
SOURCE SRC1
|
||||
REMOTE_ID "ZX82LM550000009321"
|
||||
ROUND R1
|
||||
CATEGORY C1
|
||||
USE_POOL GEO
|
||||
USE_CATEGORY "Hauptstädte"
|
||||
LAYOUT GRID_2D
|
||||
SIZE 4x4
|
||||
PHASE NORMAL
|
||||
QUESTION_SET NORMAL
|
||||
CYCLES 2
|
||||
TURNS_PER_CYCLE 4
|
||||
TURN_ORDER ROTATING_START
|
||||
PICK_MODE FREE_UNANSWERED
|
||||
TEAM_MODE SOLO
|
||||
SCORING SOLO_PLUS1_NO_PENALTY
|
||||
""";
|
||||
|
||||
final DqflDocument doc = Dqfl.parseAndValidate(input);
|
||||
final DqflCategory c1 = doc.getRounds().get(0).getCategories().get(0);
|
||||
|
||||
assertEquals("V1_1", doc.getDqflVersion());
|
||||
assertEquals("GEO", c1.getUsePool());
|
||||
assertEquals("Hauptstädte", c1.getUseCategory());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("USE_CATEGORY ohne USE_POOL wird als Fehler erkannt")
|
||||
void useCategoryWithoutUsePool() throws Exception
|
||||
{
|
||||
final String input = """
|
||||
DQFL_VERSION V1_1
|
||||
FLOW "Test"
|
||||
SOURCE SRC1
|
||||
TYPE REST
|
||||
CONNECTION CONN1
|
||||
POOL P1
|
||||
SOURCE SRC1
|
||||
REMOTE_ID "AB12CD340000004711"
|
||||
ROUND R1
|
||||
CATEGORY C1
|
||||
USE_CATEGORY "Test"
|
||||
PHASE NORMAL
|
||||
TEAM_MODE SOLO
|
||||
SCORING SOLO_PLUS1_NO_PENALTY
|
||||
""";
|
||||
|
||||
final DqflDocument doc = Dqfl.parse(input);
|
||||
final DqflValidationResult result = Dqfl.validate(doc);
|
||||
|
||||
assertFalse(result.isValid());
|
||||
assertTrue(result.getErrorsOnly().stream()
|
||||
.anyMatch(e -> e.getMessage().contains("USE_CATEGORY without USE_POOL")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Category ohne USE_CATEGORY bleibt null (V1 Abwärtskompatibilität)")
|
||||
void noCategoryFilterReturnsNull() throws Exception
|
||||
{
|
||||
final DqflDocument doc = loadReferenceDocument();
|
||||
final DqflCategory c1 = doc.getRounds().get(0).getCategories().get(0);
|
||||
|
||||
assertNull(c1.getUseCategory());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V1_1 Dokument mit und ohne USE_CATEGORY gemischt")
|
||||
void mixedCategoriesWithAndWithout() throws Exception
|
||||
{
|
||||
final String input = """
|
||||
DQFL_VERSION V1_1
|
||||
FLOW "Mixed Test"
|
||||
SOURCE SRC1
|
||||
TYPE REST
|
||||
CONNECTION CONN1
|
||||
POOL HISTORY
|
||||
SOURCE SRC1
|
||||
REMOTE_ID "AB12CD340000004711"
|
||||
POOL GEO
|
||||
SOURCE SRC1
|
||||
REMOTE_ID "ZX82LM550000009321"
|
||||
ROUND R1
|
||||
CATEGORY C1
|
||||
USE_POOL HISTORY
|
||||
LAYOUT GRID_2D
|
||||
SIZE 6x6
|
||||
PHASE NORMAL
|
||||
QUESTION_SET NORMAL
|
||||
CYCLES 3
|
||||
TURNS_PER_CYCLE 4
|
||||
TURN_ORDER ROTATING_START
|
||||
PICK_MODE FREE_UNANSWERED
|
||||
TEAM_MODE SOLO
|
||||
SCORING SOLO_PLUS1_NO_PENALTY
|
||||
CATEGORY C2
|
||||
USE_POOL GEO
|
||||
USE_CATEGORY "Hauptstädte"
|
||||
LAYOUT GRID_2D
|
||||
SIZE 4x4
|
||||
PHASE NORMAL
|
||||
QUESTION_SET NORMAL
|
||||
CYCLES 2
|
||||
TURNS_PER_CYCLE 4
|
||||
TURN_ORDER ROTATING_START
|
||||
PICK_MODE FREE_UNANSWERED
|
||||
TEAM_MODE SOLO
|
||||
SCORING SOLO_PLUS1_NO_PENALTY
|
||||
""";
|
||||
|
||||
final DqflDocument doc = Dqfl.parseAndValidate(input);
|
||||
final var categories = doc.getAllCategoriesFlat();
|
||||
|
||||
assertEquals(2, categories.size());
|
||||
assertNull(categories.get(0).getUseCategory());
|
||||
assertEquals("Hauptstädte", categories.get(1).getUseCategory());
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// GridSize Tests
|
||||
// ================================================================
|
||||
|
||||
Reference in New Issue
Block a user