Use jaxb serialization for import/export

This commit is contained in:
Claude Brisson
2023-06-22 17:02:24 +02:00
parent c6157d687c
commit 0c9b50822f
3 changed files with 301 additions and 98 deletions

View File

@@ -66,6 +66,26 @@
</webApp>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>gen-schema</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>org.jeudego.pairgoth.opengotha</packageName>
<sourceType>XmlSchema</sourceType>
<sources>
<source>src/main/resources/xsd</source>
</sources>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
@@ -213,6 +233,17 @@
<artifactId>jgrapht-core</artifactId>
<version>1.5.2</version>
</dependency>
<!-- xml class generation -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.3</version>
</dependency>
<!-- tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>

View File

@@ -1,116 +1,55 @@
package org.jeudego.pairgoth.ext
import jakarta.xml.bind.JAXBContext
import jakarta.xml.bind.JAXBElement
import kotlinx.datetime.LocalDate
import org.jeudego.pairgoth.model.*
import org.jeudego.pairgoth.opengotha.TournamentType
import org.jeudego.pairgoth.opengotha.ObjectFactory
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.util.XmlFormat
import org.jeudego.pairgoth.util.booleanAttr
import org.jeudego.pairgoth.util.arrayOf
import org.jeudego.pairgoth.util.dateAttr
import org.jeudego.pairgoth.util.doubleAttr
import org.jeudego.pairgoth.util.intAttr
import org.jeudego.pairgoth.util.mutableArrayOf
import org.jeudego.pairgoth.util.objectOf
import org.jeudego.pairgoth.util.optBoolean
import org.jeudego.pairgoth.util.stringAttr
import org.w3c.dom.Element
import java.util.*
import javax.xml.XMLConstants
import javax.xml.datatype.XMLGregorianCalendar
import javax.xml.validation.SchemaFactory
class OpenGothaFormat(xml: Element): XmlFormat(xml) {
val Players by mutableArrayOf<Player>()
val Games by mutableArrayOf<Game>()
val TournamentParameterSet by objectOf<Params>()
class Player(xml: Element): XmlFormat(xml) {
val agaId by stringAttr()
val club by stringAttr()
val country by stringAttr()
val egfPin by stringAttr()
val ffgLicence by stringAttr()
val firstName by stringAttr()
val name by stringAttr()
val participating by stringAttr()
val rank by stringAttr()
val rating by intAttr()
}
class Game(xml: Element): XmlFormat(xml) {
val blackPlayer by stringAttr()
val whitePlayer by stringAttr()
val handicap by intAttr()
val knownColor by booleanAttr()
val result by stringAttr()
val roundNumber by intAttr()
}
class Params(xml: Element): XmlFormat(xml) {
val GeneralParameterSet by objectOf<GenParams>()
val HandicapParameterSet by objectOf<HandicapParams>()
val PairingParameterSet by objectOf<PairingParams>()
val PlacementParameterSet by objectOf<Criteria>()
class GenParams(xml: Element): XmlFormat(xml) {
val bInternet by optBoolean()
val basicTime by intAttr()
val beginDate by dateAttr()
val canByoYomiTime by intAttr()
val complementaryTimeSystem by stringAttr()
val endDate by dateAttr()
val fisherTime by intAttr()
val genCountNotPlayedGamesAsHalfPoint by booleanAttr()
val genMMBar by stringAttr()
val genMMFloor by stringAttr()
val komi by doubleAttr()
val location by stringAttr()
val name by stringAttr()
val nbMovesCanTime by intAttr()
val numberOfCategories by intAttr()
val numberOfRounds by intAttr()
val shortName by stringAttr()
val size by intAttr()
val stdByoYomiTime by intAttr()
}
class HandicapParams(xml: Element): XmlFormat(xml) {
val hdBasedOnMMS by booleanAttr()
val hdCeiling by intAttr()
val hdCorrection by intAttr()
val hdNoHdRankThreshold by stringAttr()
}
class PairingParams(xml: Element): XmlFormat(xml) {
val paiMaSeedSystem1 by stringAttr()
val paiMaSeedSystem2 by stringAttr()
}
class Criteria(xml: Element): XmlFormat(xml) {
val PlacementCriterion by arrayOf<Criterion>()
}
class Criterion(xml: Element): XmlFormat(xml) {
val name by stringAttr()
}
}
}
private const val MILLISECONDS_PER_DAY = 86400000
fun XMLGregorianCalendar.toLocalDate() = LocalDate.fromEpochDays((toGregorianCalendar().time.time / MILLISECONDS_PER_DAY).toInt())
object OpenGotha {
fun import(element: Element): Tournament<*> {
val imported = OpenGothaFormat(element)
val genParams = imported.TournamentParameterSet.GeneralParameterSet
val handParams = imported.TournamentParameterSet.HandicapParameterSet
val pairingParams = imported.TournamentParameterSet.PairingParameterSet
val placementParams = imported.TournamentParameterSet.PlacementParameterSet
val context = JAXBContext.newInstance(ObjectFactory::class.java)
val parsed = context.createUnmarshaller()/*.also { unmarshaller ->
val schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
val schemaURL = OpenGotha::class.java.getResource("/xsd/opengotha.xsd") ?: throw Error("opengotha.xsd not found")
val schema = schemaFactory.newSchema(schemaURL)
unmarshaller.schema = schema
}*/.unmarshal(element) as JAXBElement<TournamentType>
val ogt = parsed.value
// import tournament parameters
val genParams = ogt.tournamentParameterSet.generalParameterSet
val handParams = ogt.tournamentParameterSet.handicapParameterSet
val placmtParams = ogt.tournamentParameterSet.placementParameterSet
val pairParams = ogt.tournamentParameterSet.pairingParameterSet
val tournament = StandardTournament(
id = Store.nextTournamentId,
type = Tournament.Type.INDIVIDUAL, // CB for now, TODO
name = genParams.name,
shortName = genParams.shortName,
startDate = genParams.beginDate,
endDate = genParams.endDate,
startDate = genParams.beginDate.toLocalDate(),
endDate = genParams.endDate.toLocalDate(),
country = "FR", // no country in opengotha format
location = genParams.location,
online = genParams.bInternet ?: false,
online = genParams.isBInternet ?: false,
timeSystem = when (genParams.complementaryTimeSystem) {
"SUDDENDEATH" -> SuddenDeath(genParams.basicTime)
"STDBYOYOMI" -> StandardByoyomi(genParams.basicTime, genParams.stdByoYomiTime, 1) // no periods?
"CANBYOYOMI" -> CanadianByoyomi(genParams.basicTime, genParams.canByoYomiTime, genParams.nbMovesCanTime)
"FISCHER" -> FischerTime(genParams.basicTime, genParams.fisherTime)
"FISCHER" -> FischerTime(genParams.basicTime, genParams.fischerTime)
else -> throw Error("missing byoyomi type")
},
pairing = when (handParams.hdCeiling) {
@@ -119,7 +58,11 @@ object OpenGotha {
),
placementParams = PlacementParams(
crit = placementParams.PlacementCriterion.map { Criterion.valueOf(it.name) }.toTypedArray()
crit = placmtParams.placementCriteria.placementCriterion.filter {
it.name != "NULL"
}.map {
Criterion.valueOf(it.name)
}.toTypedArray()
)
) // TODO
else -> MacMahon(
@@ -133,8 +76,10 @@ object OpenGotha {
},
rounds = genParams.numberOfRounds
)
val canonicMap = mutableMapOf<String, Int>()
imported.Players.map { player ->
// import players
ogt.players.player.map { player ->
Player(
id = Store.nextPlayerId,
name = player.name,
@@ -147,7 +92,7 @@ object OpenGotha {
canonicMap.put("${player.name}${player.firstName}".uppercase(Locale.ENGLISH), it.id)
}
}.associateByTo(tournament.players) { it.id }
val gamesPerRound = imported.Games.groupBy {
val gamesPerRound = ogt.games.game.groupBy {
it.roundNumber
}.values.map {
it.map { game ->
@@ -169,7 +114,7 @@ object OpenGotha {
}.associateBy { it.id }.toMutableMap()
}
gamesPerRound.forEachIndexed { index, games ->
tournament.games(index).putAll(games)
tournament.games(index + 1).putAll(games)
}
return tournament
}
@@ -178,8 +123,9 @@ object OpenGotha {
fun export(tournament: Tournament<*>): String {
// two methods here
// method 1 (advised because it's more error-proof but more complex to set up) is to assign one by one
// the fields of an OpenGothaFormat instance, then call toPrettyString() on it
// opengotha = OpenGothaFormat()
// the fields of an OGTournamentType instance, then call toPrettyString() on it
// val ogt = OGTournamentType()
// ...
//
// method 2 (quick and dirty) is to rely on templating:
val xml = """
@@ -281,6 +227,7 @@ object OpenGotha {
</Tournament>
""".trimIndent()
return xml
}
}

View File

@@ -0,0 +1,225 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Tournament" type="TournamentType"/>
<xs:complexType name="PlayerType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="agaExpirationDate" use="optional"/>
<xs:attribute type="xs:string" name="agaId" use="optional"/>
<xs:attribute type="xs:string" name="club" use="optional"/>
<xs:attribute type="xs:string" name="country" use="optional"/>
<xs:attribute type="xs:string" name="egfPin" use="optional"/>
<xs:attribute type="xs:int" name="ffgLicence" use="optional"/>
<xs:attribute type="xs:string" name="ffgLicenceStatus" use="optional"/>
<xs:attribute type="xs:string" name="firstName"/>
<xs:attribute type="xs:string" name="grade" use="optional"/>
<xs:attribute type="xs:string" name="name"/>
<xs:attribute type="xs:integer" name="participating"/>
<xs:attribute type="xs:string" name="rank" use="optional"/>
<xs:attribute type="xs:int" name="rating" use="optional"/>
<xs:attribute type="xs:string" name="ratingOrigin" use="optional"/>
<xs:attribute type="xs:string" name="registeringStatus" use="optional"/>
<xs:attribute type="xs:int" name="smmsCorrection" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="PlayersType">
<xs:sequence>
<xs:element type="PlayerType" name="Player" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="GameType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="blackPlayer"/>
<xs:attribute type="xs:int" name="handicap"/>
<xs:attribute type="xs:string" name="knownColor" use="optional"/>
<xs:attribute type="xs:string" name="result" use="optional"/>
<xs:attribute type="xs:int" name="roundNumber"/>
<xs:attribute type="xs:int" name="tableNumber"/>
<xs:attribute type="xs:string" name="whitePlayer"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="GamesType">
<xs:sequence>
<xs:element type="GameType" name="Game" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ByePlayerType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="player"/>
<xs:attribute type="xs:int" name="roundNumber"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="ByePlayersType">
<xs:sequence>
<xs:element type="ByePlayerType" name="ByePlayer"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="GeneralParameterSetType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:boolean" name="bInternet" use="optional"/>
<xs:attribute type="xs:int" name="basicTime"/>
<xs:attribute type="xs:date" name="beginDate"/>
<xs:attribute type="xs:int" name="canByoYomiTime"/>
<xs:attribute type="xs:string" name="complementaryTimeSystem"/>
<xs:attribute type="xs:string" name="director"/>
<xs:attribute type="xs:date" name="endDate"/>
<xs:attribute type="xs:int" name="fischerTime"/>
<xs:attribute type="xs:string" name="genCountNotPlayedGamesAsHalfPoint"/>
<xs:attribute type="xs:string" name="genMMBar"/>
<xs:attribute type="xs:string" name="genMMFloor"/>
<xs:attribute type="xs:int" name="genMMS2ValueAbsent"/>
<xs:attribute type="xs:int" name="genMMS2ValueBye"/>
<xs:attribute type="xs:string" name="genMMZero"/>
<xs:attribute type="xs:int" name="genNBW2ValueAbsent"/>
<xs:attribute type="xs:int" name="genNBW2ValueBye"/>
<xs:attribute type="xs:string" name="genRoundDownNBWMMS"/>
<xs:attribute type="xs:float" name="komi"/>
<xs:attribute type="xs:string" name="location"/>
<xs:attribute type="xs:string" name="name"/>
<xs:attribute type="xs:int" name="nbMovesCanTime"/>
<xs:attribute type="xs:int" name="numberOfCategories"/>
<xs:attribute type="xs:int" name="numberOfRounds"/>
<xs:attribute type="xs:string" name="shortName"/>
<xs:attribute type="xs:int" name="size"/>
<xs:attribute type="xs:int" name="stdByoYomiTime"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="HandicapParameterSetType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="hdBasedOnMMS"/>
<xs:attribute type="xs:int" name="hdCeiling"/>
<xs:attribute type="xs:int" name="hdCorrection"/>
<xs:attribute type="xs:string" name="hdNoHdRankThreshold"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="PlacementCriterionType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="name" use="optional"/>
<xs:attribute type="xs:int" name="number" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="PlacementCriteriaType">
<xs:sequence>
<xs:element type="PlacementCriterionType" name="PlacementCriterion" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PlacementParameterSetType">
<xs:sequence>
<xs:element type="PlacementCriteriaType" name="PlacementCriteria"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PairingParameterSetType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:long" name="paiBaAvoidDuplGame"/>
<xs:attribute type="xs:int" name="paiBaBalanceWB"/>
<xs:attribute type="xs:string" name="paiBaDeterministic"/>
<xs:attribute type="xs:int" name="paiBaRandom"/>
<xs:attribute type="xs:string" name="paiMaAdditionalPlacementCritSystem1"/>
<xs:attribute type="xs:string" name="paiMaAdditionalPlacementCritSystem2"/>
<xs:attribute type="xs:int" name="paiMaAvoidMixingCategories"/>
<xs:attribute type="xs:string" name="paiMaCompensateDUDD"/>
<xs:attribute type="xs:string" name="paiMaDUDDLowerMode"/>
<xs:attribute type="xs:string" name="paiMaDUDDUpperMode"/>
<xs:attribute type="xs:int" name="paiMaDUDDWeight"/>
<xs:attribute type="xs:int" name="paiMaLastRoundForSeedSystem1"/>
<xs:attribute type="xs:int" name="paiMaMaximizeSeeding"/>
<xs:attribute type="xs:long" name="paiMaMinimizeScoreDifference"/>
<xs:attribute type="xs:string" name="paiMaSeedSystem1"/>
<xs:attribute type="xs:string" name="paiMaSeedSystem2"/>
<xs:attribute type="xs:int" name="paiSeAvoidSameGeo"/>
<xs:attribute type="xs:string" name="paiSeBarThresholdActive"/>
<xs:attribute type="xs:long" name="paiSeDefSecCrit"/>
<xs:attribute type="xs:int" name="paiSeMinimizeHandicap"/>
<xs:attribute type="xs:string" name="paiSeNbWinsThresholdActive"/>
<xs:attribute type="xs:int" name="paiSePreferMMSDiffRatherThanSameClub"/>
<xs:attribute type="xs:int" name="paiSePreferMMSDiffRatherThanSameCountry"/>
<xs:attribute type="xs:string" name="paiSeRankThreshold"/>
<xs:attribute type="xs:float" name="paiStandardNX1Factor"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="DPParameterSetType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="displayClCol"/>
<xs:attribute type="xs:string" name="displayCoCol"/>
<xs:attribute type="xs:string" name="displayIndGamesInMatches"/>
<xs:attribute type="xs:string" name="displayNPPlayers"/>
<xs:attribute type="xs:string" name="displayNumCol"/>
<xs:attribute type="xs:string" name="displayPlCol"/>
<xs:attribute type="xs:string" name="gameFormat"/>
<xs:attribute type="xs:string" name="playerSortType"/>
<xs:attribute type="xs:string" name="showByePlayer"/>
<xs:attribute type="xs:string" name="showNotFinallyRegisteredPlayers"/>
<xs:attribute type="xs:string" name="showNotPairedPlayers"/>
<xs:attribute type="xs:string" name="showNotParticipatingPlayers"/>
<xs:attribute type="xs:string" name="showPlayerClub"/>
<xs:attribute type="xs:string" name="showPlayerCountry"/>
<xs:attribute type="xs:string" name="showPlayerGrade"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="PublishParameterSetType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="exportToLocalFile"/>
<xs:attribute type="xs:string" name="htmlAutoScroll"/>
<xs:attribute type="xs:string" name="print"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="TournamentParameterSetType">
<xs:sequence>
<xs:element type="GeneralParameterSetType" name="GeneralParameterSet"/>
<xs:element type="HandicapParameterSetType" name="HandicapParameterSet"/>
<xs:element type="PlacementParameterSetType" name="PlacementParameterSet"/>
<xs:element type="PairingParameterSetType" name="PairingParameterSet"/>
<xs:element type="DPParameterSetType" name="DPParameterSet"/>
<xs:element type="PublishParameterSetType" name="PublishParameterSet"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TeamGeneralParameterSetType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:int" name="teamSize"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="TeamPlacementParameterSetType">
<xs:sequence>
<xs:element type="PlacementCriteriaType" name="PlacementCriteria"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TeamTournamentParameterSetType">
<xs:sequence>
<xs:element type="TeamGeneralParameterSetType" name="TeamGeneralParameterSet"/>
<xs:element type="TeamPlacementParameterSetType" name="TeamPlacementParameterSet"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TournamentType">
<xs:sequence>
<xs:element type="PlayersType" name="Players"/>
<xs:element type="GamesType" name="Games"/>
<xs:element type="ByePlayersType" name="ByePlayers"/>
<xs:element type="TournamentParameterSetType" name="TournamentParameterSet"/>
<xs:element type="TeamTournamentParameterSetType" name="TeamTournamentParameterSet"/>
</xs:sequence>
<xs:attribute type="xs:int" name="dataVersion"/>
<xs:attribute type="xs:string" name="externalIPAddress"/>
<xs:attribute type="xs:float" name="fullVersionNumber"/>
<xs:attribute type="xs:string" name="runningMode"/>
<xs:attribute type="xs:long" name="saveDT"/>
</xs:complexType>
</xs:schema>