From b0ccc7fef9b3191db0dab269f87c3ae3bf92684b Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 09:54:04 +0200 Subject: [PATCH 01/11] Upgrade Gradle wrapper from 7.0 to 8.10.2 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 78e8f7b..f3407b9 100644 --- a/build.gradle +++ b/build.gradle @@ -107,7 +107,7 @@ signing { } wrapper { - gradleVersion = "7.0" + gradleVersion = "8.10.2" distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3c4101c..18330fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 2f40a465f167725c2e759ccdf0765dc98e8713f8 Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Tue, 5 May 2026 19:39:49 +0200 Subject: [PATCH 02/11] Suppress Javadoc doclint warnings to reduce build output noise --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index f3407b9..a6f92ae 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,10 @@ test { useJUnitPlatform() } +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') +} + group = 'de.cronn' version = System.env.ARTIFACT_VERSION ?: 'SNAPSHOT' From df3648a700ca00fe506431e95ca417a5d371672e Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 08:21:54 +0200 Subject: [PATCH 03/11] Fix thread-safety bug in DateTimeReplacer --- .../replacements/DateTimeReplacer.java | 16 +++---- .../replacements/DateTimeReplacerTest.java | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java b/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java index 924d32d..aca02d7 100644 --- a/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java +++ b/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java @@ -11,12 +11,12 @@ public class DateTimeReplacer implements ValidationNormalizer { private static final String DATE_TIME_GROUP_NAME = "DateTime"; - private Matcher matcher; - private DateTimeFormatter sourceFormat; - private DateTimeFormatter destinationFormat; + private final Pattern pattern; + private final DateTimeFormatter sourceFormat; + private final DateTimeFormatter destinationFormat; public DateTimeReplacer(Pattern pattern, DateTimeFormatter sourceFormat, DateTimeFormatter destinationFormat) { - this.matcher = pattern.matcher(""); + this.pattern = pattern; this.sourceFormat = sourceFormat; this.destinationFormat = destinationFormat; } @@ -25,7 +25,7 @@ public DateTimeReplacer(Pattern pattern, DateTimeFormatter sourceFormat, DateTim public String normalize(String textToNormalize) { StringBuilder normalizedResultBuilder = new StringBuilder(); - matcher.reset(textToNormalize); + Matcher matcher = pattern.matcher(textToNormalize); int endOfLastMatchIndex = 0; boolean matchFound = false; @@ -40,7 +40,7 @@ public String normalize(String textToNormalize) { String match = textToNormalize.substring(startOfCurrentMatchIndex, endOfCurrentMatchIndex); try { - String possiblyDateTime = matchDateTime(textToNormalize); + String possiblyDateTime = matchDateTime(textToNormalize, matcher); String formattedDateTime = matchDateTimeAndConvertFromSourceToDestinationFormat(possiblyDateTime); normalizedResultBuilder.append(match.replace(possiblyDateTime, formattedDateTime)); } catch (DateTimeParseException e) { @@ -65,7 +65,7 @@ private String matchDateTimeAndConvertFromSourceToDestinationFormat(String possi return destinationFormat.format(parsedDateTime); } - private String matchDateTime(String text) { + private String matchDateTime(String text, Matcher matcher) { return text.substring(matcher.start(DATE_TIME_GROUP_NAME), matcher.end(DATE_TIME_GROUP_NAME)); } @@ -75,7 +75,7 @@ private void appendFromSourceToResult(String source, StringBuilder resultBuilder @Override public String toString() { - return "DateTimeReplacer for pattern " + matcher.pattern().toString() + "."; + return "DateTimeReplacer for pattern " + pattern.toString() + "."; } } diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java index 69facb7..293be87 100644 --- a/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java +++ b/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java @@ -4,6 +4,13 @@ import static org.assertj.core.api.Assertions.*; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; @@ -87,6 +94,42 @@ void testReplaceDateTimeInXml() { assertThat(actual).isEqualTo(expected); } + @Test + void testNormalizeIsThreadSafe() throws Exception { + DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("(?.+)"), + ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE); + + String[] inputs = { "2017-09-04T15:39:31.31+02:00", "2020-12-25T10:00:00.00+02:00" }; + String[] expectedOutputs = { "2017-09-04", "2020-12-25" }; + + int threadCount = 50; + CountDownLatch startLatch = new CountDownLatch(1); + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(); + + for (int i = 0; i < threadCount; i++) { + String input = inputs[i % 2]; + String expected = expectedOutputs[i % 2]; + futures.add(executor.submit(() -> { + startLatch.await(); + for (int j = 0; j < 500; j++) { + if (!expected.equals(dateTimeReplacer.normalize(input))) { + return false; + } + } + return true; + })); + } + + startLatch.countDown(); + executor.shutdown(); + executor.awaitTermination(30, TimeUnit.SECONDS); + + for (Future future : futures) { + assertThat(future.get()).isTrue(); + } + } + @Test void testToString() throws Exception { ValidationNormalizer normalizer = new XmlDateTimeReplacerBuilder().withElementName("date") From ab9f3bf72579f53f3c72a73ce7f3274b24330b1e Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 08:42:01 +0200 Subject: [PATCH 04/11] Add UuidNormalizer --- .../normalization/UuidNormalizer.java | 16 +++++++ .../normalization/UuidNormalizerTest.java | 48 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java create mode 100644 src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java b/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java new file mode 100644 index 0000000..0a75d56 --- /dev/null +++ b/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java @@ -0,0 +1,16 @@ +package de.cronn.assertions.validationfile.normalization; + +public class UuidNormalizer implements ValidationNormalizer { + + private final ValidationNormalizer delegate = + new IdNormalizer( + new IncrementingIdProvider(), + "UUID_", + "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); + + @Override + public String normalize(String source) { + return delegate.normalize(source); + } + +} diff --git a/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java b/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java new file mode 100644 index 0000000..18b2e7f --- /dev/null +++ b/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java @@ -0,0 +1,48 @@ +package de.cronn.assertions.validationfile.normalization; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class UuidNormalizerTest { + + @Test + void testNormalizeLowercase() { + String input = "id=550e8400-e29b-41d4-a716-446655440000"; + String expected = "id=UUID_1"; + + assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected); + } + + @Test + void testNormalizeUppercase() { + String input = "id=550E8400-E29B-41D4-A716-446655440000"; + String expected = "id=UUID_1"; + + assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected); + } + + @Test + void testNormalizeMixedCase() { + String input = "id=550e8400-E29B-41d4-A716-446655440000"; + String expected = "id=UUID_1"; + + assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected); + } + + @Test + void testNormalizeMultipleUuids() { + String input = "a=550e8400-e29b-41d4-a716-446655440000 b=661f9511-f30c-52e5-b827-557766551111 a=550e8400-e29b-41d4-a716-446655440000"; + String expected = "a=UUID_1 b=UUID_2 a=UUID_1"; + + assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected); + } + + @Test + void testNoUuid() { + String input = "no uuids here"; + + assertThat(new UuidNormalizer().normalize(input)).isEqualTo(input); + } + +} From 8b11e451ca6f2101ed3d38397772d182945d9dde Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 16:01:44 +0200 Subject: [PATCH 05/11] Migrate build scripts from Groovy DSL to Kotlin DSL --- build.gradle | 120 ---------------------------- build.gradle.kts | 116 +++++++++++++++++++++++++++ configuration-test/build.gradle | 35 -------- configuration-test/build.gradle.kts | 36 +++++++++ settings.gradle | 2 - settings.gradle.kts | 2 + 6 files changed, 154 insertions(+), 157 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 configuration-test/build.gradle create mode 100644 configuration-test/build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index a6f92ae..0000000 --- a/build.gradle +++ /dev/null @@ -1,120 +0,0 @@ -plugins { - id 'java-library' - id 'maven-publish' - id 'signing' -} - -repositories { - mavenCentral() -} - -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 - -compileJava.options.encoding = 'UTF-8' - -java { - withSourcesJar() - withJavadocJar() - - registerFeature('junit5Support') { - usingSourceSet(sourceSets.main) - } -} - -dependencies { - junit5SupportImplementation "org.junit.jupiter:junit-jupiter-api:latest.release" - api "com.googlecode.java-diff-utils:diffutils:latest.release" - api "org.opentest4j:opentest4j:latest.release" - - testImplementation "org.junit.jupiter:junit-jupiter:latest.release" - testImplementation "org.assertj:assertj-core:latest.release" - testImplementation "com.fasterxml.jackson.core:jackson-databind" - testImplementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" - testImplementation platform("com.fasterxml.jackson:jackson-bom:latest.release") - // Filter release candidates and beta versions - components.all { ComponentMetadataDetails details -> - if (details.id.version =~ /(?i).+[-.](CANDIDATE|RC|BETA|ALPHA|M\d+).*/) { - details.status = 'milestone' - } - } -} - -test { - useJUnitPlatform() -} - -javadoc { - options.addStringOption('Xdoclint:none', '-quiet') -} - - -group = 'de.cronn' -version = System.env.ARTIFACT_VERSION ?: 'SNAPSHOT' - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.group - artifactId project.name - version project.version - from components.java - - pom { - name = project.name - description = "File Based Assertions for Java" - url = "https://github.com/cronn/validation-file-assertions" - - licenses { - license { - name = "The Apache Software License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } - - developers { - developer { - name = "Radosław Postołowicz" - email = "radoslaw.postolowicz@cronn.de" - } - } - - scm { - url = "https://github.com/cronn/validation-file-assertions" - } - - versionMapping { - usage('java-api') { - fromResolutionOf('runtimeClasspath') - } - usage('java-runtime') { - fromResolutionResult() - } - } - } - - } - } - repositories { - maven { - url "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2" - credentials { - username = project.hasProperty('nexusUsername') ? project.property('nexusUsername') : System.getenv('NEXUS_USERNAME') - password = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : System.getenv('NEXUS_PASSWORD') - } - } - } -} - -signing { - sign publishing.publications.mavenJava -} - -wrapper { - gradleVersion = "8.10.2" - distributionType = Wrapper.DistributionType.ALL -} - -dependencyLocking { - lockAllConfigurations() -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a86c3cd --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,116 @@ +plugins { + `java-library` + `maven-publish` + signing +} + +repositories { + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() + withJavadocJar() + + registerFeature("junit5Support") { + usingSourceSet(sourceSets["main"]) + } +} + +tasks.withType { + options.encoding = "UTF-8" +} + +dependencies { + "junit5SupportImplementation"("org.junit.jupiter:junit-jupiter-api:latest.release") + api("com.googlecode.java-diff-utils:diffutils:latest.release") + api("org.opentest4j:opentest4j:latest.release") + + testImplementation("org.junit.jupiter:junit-jupiter:latest.release") + testImplementation("org.assertj:assertj-core:latest.release") + testImplementation("com.fasterxml.jackson.core:jackson-databind") + testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") + testImplementation(platform("com.fasterxml.jackson:jackson-bom:latest.release")) + + components.all { + if (id.version.matches(Regex("(?i).+[-.](CANDIDATE|RC|BETA|ALPHA|M\\d+).*"))) { + status = "milestone" + } + } +} + +tasks.test { + useJUnitPlatform() +} + +tasks.javadoc { + (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") +} + +group = "de.cronn" +version = System.getenv("ARTIFACT_VERSION") ?: "SNAPSHOT" + +publishing { + publications { + create("mavenJava") { + from(components["java"]) + + pom { + name = project.name + description = "File Based Assertions for Java" + url = "https://github.com/cronn/validation-file-assertions" + + licenses { + license { + name = "The Apache Software License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + + developers { + developer { + name = "Radosław Postołowicz" + email = "radoslaw.postolowicz@cronn.de" + } + } + + scm { + url = "https://github.com/cronn/validation-file-assertions" + } + + versionMapping { + usage("java-api") { + fromResolutionOf("runtimeClasspath") + } + usage("java-runtime") { + fromResolutionResult() + } + } + } + } + } + repositories { + maven { + url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2") + credentials { + username = if (project.hasProperty("nexusUsername")) project.property("nexusUsername").toString() else System.getenv("NEXUS_USERNAME") + password = if (project.hasProperty("nexusPassword")) project.property("nexusPassword").toString() else System.getenv("NEXUS_PASSWORD") + } + } + } +} + +signing { + sign(publishing.publications["mavenJava"]) +} + +tasks.wrapper { + gradleVersion = "8.10.2" + distributionType = Wrapper.DistributionType.ALL +} + +dependencyLocking { + lockAllConfigurations() +} diff --git a/configuration-test/build.gradle b/configuration-test/build.gradle deleted file mode 100644 index d76df94..0000000 --- a/configuration-test/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -plugins { - id 'java' -} - -group = 'de.cronn' -version = '0.0.1-SNAPSHOT' - -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 - -compileJava.options.encoding = 'UTF-8' - -java { - withSourcesJar() - withJavadocJar() -} - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':') - - testImplementation "org.junit.jupiter:junit-jupiter:latest.release" - testImplementation "org.assertj:assertj-core:latest.release" -} - -tasks.named('test') { - useJUnitPlatform() -} - -dependencyLocking { - lockAllConfigurations() -} diff --git a/configuration-test/build.gradle.kts b/configuration-test/build.gradle.kts new file mode 100644 index 0000000..8716ff3 --- /dev/null +++ b/configuration-test/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + java +} + +group = "de.cronn" +version = "0.0.1-SNAPSHOT" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() + withJavadocJar() +} + +tasks.withType { + options.encoding = "UTF-8" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":")) + + testImplementation("org.junit.jupiter:junit-jupiter:latest.release") + testImplementation("org.assertj:assertj-core:latest.release") +} + +tasks.test { + useJUnitPlatform() +} + +dependencyLocking { + lockAllConfigurations() +} diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index d871afd..0000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'validation-file-assertions' -include ':configuration-test' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..6a0d107 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "validation-file-assertions" +include(":configuration-test") From a91e26adc81a52e1b9e81fcfca5ead0389e5dfab Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 16:31:04 +0200 Subject: [PATCH 06/11] Update dependencies Pin JUnit to 5.x due to Java 8 incompatibility with JUnit 6 --- build.gradle.kts | 5 +++-- configuration-test/build.gradle.kts | 9 ++++++++- configuration-test/gradle.lockfile | 19 ++++++++++--------- gradle.lockfile | 29 +++++++++++++++-------------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a86c3cd..18fbec7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,11 +24,12 @@ tasks.withType { } dependencies { - "junit5SupportImplementation"("org.junit.jupiter:junit-jupiter-api:latest.release") + "junit5SupportImplementation"("org.junit.jupiter:junit-jupiter-api:[5.0,6.0)") api("com.googlecode.java-diff-utils:diffutils:latest.release") api("org.opentest4j:opentest4j:latest.release") - testImplementation("org.junit.jupiter:junit-jupiter:latest.release") + testImplementation("org.junit.jupiter:junit-jupiter:[5.0,6.0)") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation("org.assertj:assertj-core:latest.release") testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") diff --git a/configuration-test/build.gradle.kts b/configuration-test/build.gradle.kts index 8716ff3..96c97a9 100644 --- a/configuration-test/build.gradle.kts +++ b/configuration-test/build.gradle.kts @@ -23,8 +23,15 @@ repositories { dependencies { implementation(project(":")) - testImplementation("org.junit.jupiter:junit-jupiter:latest.release") + testImplementation("org.junit.jupiter:junit-jupiter:[5.0,6.0)") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation("org.assertj:assertj-core:latest.release") + + components.all { + if (id.version.matches(Regex("(?i).+[-.](CANDIDATE|RC|BETA|ALPHA|M\\d+).*"))) { + status = "milestone" + } + } } tasks.test { diff --git a/configuration-test/gradle.lockfile b/configuration-test/gradle.lockfile index 5783cf4..f957a0f 100644 --- a/configuration-test/gradle.lockfile +++ b/configuration-test/gradle.lockfile @@ -2,15 +2,16 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.googlecode.java-diff-utils:diffutils:1.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath -org.assertj:assertj-core:3.24.2=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.10.0=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.10.0=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.10.0=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.10.0=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.10.0=testRuntimeClasspath -org.junit:junit-bom:5.10.0=testCompileClasspath,testRuntimeClasspath +org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.4=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.4=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.4=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.4=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.4=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.4=testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.4=testRuntimeClasspath +org.junit:junit-bom:5.14.4=testCompileClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath empty=annotationProcessor,testAnnotationProcessor diff --git a/gradle.lockfile b/gradle.lockfile index 9310060..51dd379 100644 --- a/gradle.lockfile +++ b/gradle.lockfile @@ -1,21 +1,22 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -com.fasterxml.jackson.core:jackson-annotations:2.15.2=testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-core:2.15.2=testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-databind:2.15.2=testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2=testCompileClasspath,testRuntimeClasspath -com.fasterxml.jackson:jackson-bom:2.15.2=testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.3=testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.3=testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.3=testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.3=testCompileClasspath,testRuntimeClasspath com.googlecode.java-diff-utils:diffutils:1.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=compileClasspath,testCompileClasspath -org.assertj:assertj-core:3.24.2=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:5.10.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.10.0=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.10.0=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:5.10.0=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.10.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.10.0=testRuntimeClasspath -org.junit:junit-bom:5.10.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.4=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.4=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.4=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.4=testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.4=testRuntimeClasspath +org.junit:junit-bom:5.14.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath empty=annotationProcessor,signatures,testAnnotationProcessor From 060a0cfc3e41bcebaca61a18e904e495e07d699b Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 17:15:08 +0200 Subject: [PATCH 07/11] Update CI: Update action versions --- .github/workflows/gradle-wrapper-validation.yml | 4 ++-- .github/workflows/gradle.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 4b31496..86ff6a7 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -11,5 +11,5 @@ jobs: name: "Validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1.0.4 + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v3 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c367c8b..a1f0ef3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,11 +14,11 @@ jobs: java: [ '8', '11', '16' ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} - distribution: 'adopt' + distribution: 'temurin' - name: Build with Gradle run: ./gradlew build From b04dde45f08554d09790bdba30bb18d5502a7070 Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 17:18:55 +0200 Subject: [PATCH 08/11] Bump CI Java matrix to 17/21 At least 17 is required for spotless --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a1f0ef3..033a848 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '8', '11', '16' ] + java: [ '17', '21' ] steps: - uses: actions/checkout@v4 From b1675df382f551b6dd3c8b1540c1eab088256ce2 Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Wed, 6 May 2026 16:44:04 +0200 Subject: [PATCH 09/11] Introduce Spotless for code formatting and simplify .editorconfig --- .editorconfig | 274 +---------------- build.gradle.kts | 35 ++- .../FileBasedComparisonFailure.java | 87 +++--- .../validationfile/FileExtension.java | 2 +- .../validationfile/FileExtensions.java | 46 +-- .../assertions/validationfile/TestData.java | 144 +++++---- .../ValidationFileAssertions.java | 282 ++++++++++-------- .../validationfile/config/Configuration.java | 3 +- .../JUnit5ValidationFileAssertions.java | 46 ++- .../validationfile/junit5/TestInfoStore.java | 52 ++-- ...llectionOfStringsValidationNormalizer.java | 29 +- .../normalization/ConstantIdProvider.java | 17 +- .../normalization/IdNormalizer.java | 129 ++++---- .../normalization/IdProvider.java | 2 +- .../normalization/IncrementingIdProvider.java | 23 +- .../normalization/SimpleRegexReplacement.java | 59 ++-- .../normalization/StringNormalizer.java | 16 +- .../normalization/UuidNormalizer.java | 21 +- .../normalization/ValidationNormalizer.java | 51 ++-- .../replacements/AbstractJsonReplacer.java | 17 +- .../AbstractXmlReplacerBuilder.java | 112 ++++--- .../replacements/DateTimeReplacer.java | 144 ++++----- .../JsonDateTimeReplacerBuilder.java | 72 +++-- .../replacements/JsonReplacerBuilder.java | 100 ++++--- .../validationfile/replacements/Replacer.java | 81 ++--- .../XmlDateTimeReplacerBuilder.java | 80 ++--- .../replacements/XmlReplacerBuilder.java | 91 +++--- .../util/FileBasedComparisonUtils.java | 197 ++++++------ .../validationfile/util/MarkdownTable.java | 216 +++++++------- .../validationfile/util/TestNameUtils.java | 33 +- .../FileBasedComparisonFailureTest.java | 195 ++++++------ ...rtionsTest_OverrideValidationFileName.java | 56 ++-- ...tionFileAssertionsTest_SoftAssertions.java | 97 +++--- .../CleanupValidationFilesAfterAllTests.java | 86 +++--- .../extension/ValidationFilesTestHelper.java | 72 ++--- .../JUnit5ValidationFileAssertionsTest.java | 170 ++++++----- ...lidationFileAssertionsTest_Concurrent.java | 24 +- .../normalization/IdNormalizerTest.java | 219 +++++++------- ...ListOfStringsValidationNormalizerTest.java | 26 +- .../normalization/UuidNormalizerTest.java | 60 ++-- .../AbstractXmlReplacerBuilderTest.java | 138 +++++---- .../replacements/DateTimeReplacerTest.java | 270 +++++++++-------- .../JsonDateTimeReplacerBuilderTest.java | 115 +++---- .../replacements/JsonReplacerBuilderTest.java | 99 +++--- .../replacements/ReplacerTest.java | 107 ++++--- .../XmlDateTimeReplacerBuilderTest.java | 111 +++---- .../replacements/XmlReplacerBuilderTest.java | 131 ++++---- .../sample/DummySerializer.java | 83 +++--- .../sample/SampleStructure.java | 112 +++---- .../sample/ValidationFileSampleTest.java | 157 ++++++---- ...ValidationFileSampleTest_ShowFailures.java | 22 +- ...ationFileWithSoftAssertionsSampleTest.java | 25 +- ...eBasedComparisonUtilsDataProviderTest.java | 76 ++--- .../util/FileBasedComparisonUtilsTest.java | 209 +++++++------ .../util/MarkdownTableTest.java | 80 +++-- .../util/TestNameUtilsTest.java | 27 +- 56 files changed, 2543 insertions(+), 2685 deletions(-) diff --git a/.editorconfig b/.editorconfig index dda3afa..4389f2b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,276 +4,18 @@ root = true charset = utf-8 end_of_line = lf insert_final_newline = true -ij_continuation_indent_size = 8 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = false -ij_smart_tabs = false -ij_visual_guides = none -ij_wrap_on_typing = false - -[*.md] -indent_style = space -indent_size = 4 +trim_trailing_whitespace = true +max_line_length = 120 [*.yml] indent_style = space indent_size = 2 -[*.{java, gradle, groovy, xml}] -indent_style = tab -tab_width = 4 -trim_trailing_whitespace = true -max_line_length = 120 +[*.{md,xml,sh,json,kts,html}] +indent_style = space +indent_size = 4 [*.java] -ij_continuation_indent_size = 4 -ij_java_align_consecutive_assignments = false -ij_java_align_consecutive_variable_declarations = false -ij_java_align_group_field_declarations = false -ij_java_align_multiline_annotation_parameters = false -ij_java_align_multiline_array_initializer_expression = false -ij_java_align_multiline_assignment = false -ij_java_align_multiline_binary_operation = false -ij_java_align_multiline_chained_methods = false -ij_java_align_multiline_extends_list = false -ij_java_align_multiline_for = true -ij_java_align_multiline_method_parentheses = false -ij_java_align_multiline_parameters = true -ij_java_align_multiline_parameters_in_calls = false -ij_java_align_multiline_parenthesized_expression = false -ij_java_align_multiline_records = true -ij_java_align_multiline_resources = true -ij_java_align_multiline_ternary_operation = false -ij_java_align_multiline_text_blocks = false -ij_java_align_multiline_throws_list = false -ij_java_align_subsequent_simple_methods = false -ij_java_align_throws_keyword = false -ij_java_annotation_parameter_wrap = off -ij_java_array_initializer_new_line_after_left_brace = false -ij_java_array_initializer_right_brace_on_new_line = false -ij_java_array_initializer_wrap = off -ij_java_assert_statement_colon_on_next_line = false -ij_java_assert_statement_wrap = off -ij_java_assignment_wrap = off -ij_java_binary_operation_sign_on_next_line = false -ij_java_binary_operation_wrap = off -ij_java_blank_lines_after_anonymous_class_header = 0 -ij_java_blank_lines_after_class_header = 0 -ij_java_blank_lines_after_imports = 1 -ij_java_blank_lines_after_package = 1 -ij_java_blank_lines_around_class = 1 -ij_java_blank_lines_around_field = 0 -ij_java_blank_lines_around_field_in_interface = 0 -ij_java_blank_lines_around_initializer = 1 -ij_java_blank_lines_around_method = 1 -ij_java_blank_lines_around_method_in_interface = 1 -ij_java_blank_lines_before_class_end = 0 -ij_java_blank_lines_before_imports = 1 -ij_java_blank_lines_before_method_body = 0 -ij_java_blank_lines_before_package = 0 -ij_java_block_brace_style = end_of_line -ij_java_block_comment_at_first_column = true -ij_java_builder_methods = none -ij_java_call_parameters_new_line_after_left_paren = false -ij_java_call_parameters_right_paren_on_new_line = false -ij_java_call_parameters_wrap = off -ij_java_case_statement_on_separate_line = true -ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = split_into_lines -ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 99 -ij_java_class_names_in_javadoc = 1 -ij_java_do_not_indent_top_level_class_members = false -ij_java_do_not_wrap_after_single_annotation = false -ij_java_do_while_brace_force = never -ij_java_doc_add_blank_line_after_description = true -ij_java_doc_add_blank_line_after_param_comments = false -ij_java_doc_add_blank_line_after_return = false -ij_java_doc_add_p_tag_on_empty_lines = true -ij_java_doc_align_exception_comments = true -ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = false -ij_java_doc_enable_formatting = true -ij_java_doc_enable_leading_asterisks = true -ij_java_doc_indent_on_continuation = false -ij_java_doc_keep_empty_lines = true -ij_java_doc_keep_empty_parameter_tag = true -ij_java_doc_keep_empty_return_tag = true -ij_java_doc_keep_empty_throws_tag = true -ij_java_doc_keep_invalid_tags = true -ij_java_doc_param_description_on_new_line = false -ij_java_doc_preserve_line_breaks = false -ij_java_doc_use_throws_not_exception_tag = true -ij_java_else_on_new_line = false -ij_java_entity_dd_suffix = EJB -ij_java_entity_eb_suffix = Bean -ij_java_entity_hi_suffix = Home -ij_java_entity_lhi_prefix = Local -ij_java_entity_lhi_suffix = Home -ij_java_entity_li_prefix = Local -ij_java_entity_pk_class = java.lang.String -ij_java_entity_vo_suffix = VO -ij_java_enum_constants_wrap = off -ij_java_extends_keyword_wrap = off -ij_java_extends_list_wrap = off -ij_java_field_annotation_wrap = split_into_lines -ij_java_finally_on_new_line = false -ij_java_for_brace_force = never -ij_java_for_statement_new_line_after_left_paren = false -ij_java_for_statement_right_paren_on_new_line = false -ij_java_for_statement_wrap = off -ij_java_generate_final_locals = false -ij_java_generate_final_parameters = false -ij_java_if_brace_force = never -ij_java_imports_layout = $*, |, java.**, |, javax.**, |, org.**, |, com.**, |, * -ij_java_indent_case_from_switch = true -ij_java_insert_inner_class_imports = false -ij_java_insert_override_annotation = true -ij_java_keep_blank_lines_before_right_brace = 2 -ij_java_keep_blank_lines_between_package_declaration_and_header = 2 -ij_java_keep_blank_lines_in_code = 2 -ij_java_keep_blank_lines_in_declarations = 2 -ij_java_keep_builder_methods_indents = false -ij_java_keep_control_statement_in_one_line = true -ij_java_keep_first_column_comment = true -ij_java_keep_indents_on_empty_lines = false -ij_java_keep_line_breaks = true -ij_java_keep_multiple_expressions_in_one_line = false -ij_java_keep_simple_blocks_in_one_line = false -ij_java_keep_simple_classes_in_one_line = false -ij_java_keep_simple_lambdas_in_one_line = false -ij_java_keep_simple_methods_in_one_line = false -ij_java_label_indent_absolute = false -ij_java_label_indent_size = 0 -ij_java_lambda_brace_style = end_of_line -ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = false -ij_java_line_comment_at_first_column = true -ij_java_message_dd_suffix = EJB -ij_java_message_eb_suffix = Bean -ij_java_method_annotation_wrap = split_into_lines -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = off -ij_java_method_parameters_new_line_after_left_paren = false -ij_java_method_parameters_right_paren_on_new_line = false -ij_java_method_parameters_wrap = off -ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 1 -ij_java_new_line_after_lparen_in_record_header = false -ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.* -ij_java_parameter_annotation_wrap = off -ij_java_parentheses_expression_new_line_after_left_paren = false -ij_java_parentheses_expression_right_paren_on_new_line = false -ij_java_place_assignment_sign_on_next_line = false -ij_java_prefer_longer_names = true -ij_java_prefer_parameters_wrap = false -ij_java_record_components_wrap = normal -ij_java_repeat_synchronized = true -ij_java_replace_instanceof_and_cast = false -ij_java_replace_null_check = true -ij_java_replace_sum_lambda_with_method_ref = true -ij_java_resource_list_new_line_after_left_paren = false -ij_java_resource_list_right_paren_on_new_line = false -ij_java_resource_list_wrap = off -ij_java_rparen_on_new_line_in_record_header = false -ij_java_session_dd_suffix = EJB -ij_java_session_eb_suffix = Bean -ij_java_session_hi_suffix = Home -ij_java_session_lhi_prefix = Local -ij_java_session_lhi_suffix = Home -ij_java_session_li_prefix = Local -ij_java_session_si_suffix = Service -ij_java_space_after_closing_angle_bracket_in_type_argument = false -ij_java_space_after_colon = true -ij_java_space_after_comma = true -ij_java_space_after_comma_in_type_arguments = true -ij_java_space_after_for_semicolon = true -ij_java_space_after_quest = true -ij_java_space_after_type_cast = true -ij_java_space_before_annotation_array_initializer_left_brace = false -ij_java_space_before_annotation_parameter_list = false -ij_java_space_before_array_initializer_left_brace = true -ij_java_space_before_catch_keyword = true -ij_java_space_before_catch_left_brace = true -ij_java_space_before_catch_parentheses = true -ij_java_space_before_class_left_brace = true -ij_java_space_before_colon = true -ij_java_space_before_colon_in_foreach = true -ij_java_space_before_comma = false -ij_java_space_before_do_left_brace = true -ij_java_space_before_else_keyword = true -ij_java_space_before_else_left_brace = true -ij_java_space_before_finally_keyword = true -ij_java_space_before_finally_left_brace = true -ij_java_space_before_for_left_brace = true -ij_java_space_before_for_parentheses = true -ij_java_space_before_for_semicolon = false -ij_java_space_before_if_left_brace = true -ij_java_space_before_if_parentheses = true -ij_java_space_before_method_call_parentheses = false -ij_java_space_before_method_left_brace = true -ij_java_space_before_method_parentheses = false -ij_java_space_before_opening_angle_bracket_in_type_parameter = false -ij_java_space_before_quest = true -ij_java_space_before_switch_left_brace = true -ij_java_space_before_switch_parentheses = true -ij_java_space_before_synchronized_left_brace = true -ij_java_space_before_synchronized_parentheses = true -ij_java_space_before_try_left_brace = true -ij_java_space_before_try_parentheses = true -ij_java_space_before_type_parameter_list = false -ij_java_space_before_while_keyword = true -ij_java_space_before_while_left_brace = true -ij_java_space_before_while_parentheses = true -ij_java_space_inside_one_line_enum_braces = false -ij_java_space_within_empty_array_initializer_braces = false -ij_java_space_within_empty_method_call_parentheses = false -ij_java_space_within_empty_method_parentheses = false -ij_java_spaces_around_additive_operators = true -ij_java_spaces_around_assignment_operators = true -ij_java_spaces_around_bitwise_operators = true -ij_java_spaces_around_equality_operators = true -ij_java_spaces_around_lambda_arrow = true -ij_java_spaces_around_logical_operators = true -ij_java_spaces_around_method_ref_dbl_colon = false -ij_java_spaces_around_multiplicative_operators = true -ij_java_spaces_around_relational_operators = true -ij_java_spaces_around_shift_operators = true -ij_java_spaces_around_type_bounds_in_type_parameters = true -ij_java_spaces_around_unary_operator = false -ij_java_spaces_within_angle_brackets = false -ij_java_spaces_within_annotation_parentheses = false -ij_java_spaces_within_array_initializer_braces = true -ij_java_spaces_within_braces = false -ij_java_spaces_within_brackets = false -ij_java_spaces_within_cast_parentheses = false -ij_java_spaces_within_catch_parentheses = false -ij_java_spaces_within_for_parentheses = false -ij_java_spaces_within_if_parentheses = false -ij_java_spaces_within_method_call_parentheses = false -ij_java_spaces_within_method_parentheses = false -ij_java_spaces_within_parentheses = false -ij_java_spaces_within_record_header = false -ij_java_spaces_within_switch_parentheses = false -ij_java_spaces_within_synchronized_parentheses = false -ij_java_spaces_within_try_parentheses = false -ij_java_spaces_within_while_parentheses = false -ij_java_special_else_if_treatment = true -ij_java_subclass_name_suffix = Impl -ij_java_ternary_operation_signs_on_next_line = false -ij_java_ternary_operation_wrap = off -ij_java_test_name_suffix = Test -ij_java_throws_keyword_wrap = off -ij_java_throws_list_wrap = off -ij_java_use_external_annotations = false -ij_java_use_fq_class_names = false -ij_java_use_relative_indents = false -ij_java_use_single_class_imports = true -ij_java_variable_annotation_wrap = off -ij_java_visibility = public -ij_java_while_brace_force = never -ij_java_while_on_new_line = false -ij_java_wrap_comments = false -ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = false +indent_style = space +indent_size = 2 +max_line_length = 100 diff --git a/build.gradle.kts b/build.gradle.kts index 18fbec7..9713a7d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ plugins { `java-library` `maven-publish` signing + id("com.diffplug.spotless") version "latest.release" } repositories { @@ -96,8 +97,24 @@ publishing { maven { url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2") credentials { - username = if (project.hasProperty("nexusUsername")) project.property("nexusUsername").toString() else System.getenv("NEXUS_USERNAME") - password = if (project.hasProperty("nexusPassword")) project.property("nexusPassword").toString() else System.getenv("NEXUS_PASSWORD") + username = + if (project.hasProperty( + "nexusUsername", + ) + ) { + project.property("nexusUsername").toString() + } else { + System.getenv("NEXUS_USERNAME") + } + password = + if (project.hasProperty( + "nexusPassword", + ) + ) { + project.property("nexusPassword").toString() + } else { + System.getenv("NEXUS_PASSWORD") + } } } } @@ -107,6 +124,20 @@ signing { sign(publishing.publications["mavenJava"]) } +spotless { + java { + googleJavaFormat() + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } + kotlinGradle { + ktlint() + trimTrailingWhitespace() + endWithNewline() + } +} + tasks.wrapper { gradleVersion = "8.10.2" distributionType = Wrapper.DistributionType.ALL diff --git a/src/main/java/de/cronn/assertions/validationfile/FileBasedComparisonFailure.java b/src/main/java/de/cronn/assertions/validationfile/FileBasedComparisonFailure.java index f4918c6..9274376 100644 --- a/src/main/java/de/cronn/assertions/validationfile/FileBasedComparisonFailure.java +++ b/src/main/java/de/cronn/assertions/validationfile/FileBasedComparisonFailure.java @@ -1,54 +1,53 @@ package de.cronn.assertions.validationfile; +import difflib.Delta; +import difflib.DiffUtils; +import difflib.Patch; import java.util.Arrays; import java.util.Collections; import java.util.List; - import org.opentest4j.AssertionFailedError; -import difflib.Delta; -import difflib.DiffUtils; -import difflib.Patch; - public class FileBasedComparisonFailure extends AssertionFailedError { - private static final long serialVersionUID = 1L; - - // these fields might look unnecessary, but they are needed to support IntelliJ diff viewer - // details: https://github.com/JetBrains/intellij-community/commit/b04f577ff6897d011a4dbaa3e381a67d054fb0a2#diff-8ec837b964197c27c51323151bc3eebbR248 - private final String fExpected; - - private final String fActual; - - public FileBasedComparisonFailure(String expected, String actual, String filenameExpected, String filenameActual) { - super(buildDiff(expected, actual, filenameExpected, filenameActual), expected, actual); - this.fExpected = expected; - this.fActual = actual; - } - - private static String buildDiff(String expected, String actual, String filenameExpected, String filenameActual) { - List expectedLines = expected == null ? Collections.emptyList() : splitLines(expected); - List actualLines = actual == null ? Collections.emptyList() : splitLines(actual); - - Patch patch = DiffUtils.diff(expectedLines, actualLines); - List> deltas = patch.getDeltas(); - if (deltas.isEmpty()) { - throw new IllegalArgumentException("expected and actual values are equal"); - } - return "\n" + String.join( - "\n", - DiffUtils.generateUnifiedDiff( - "expected" + (filenameExpected != null ? "/" + filenameExpected : ""), - "actual" + (filenameActual != null ? "/" + filenameActual : ""), - expectedLines, - patch, - 3 - ) - ); - } - - private static List splitLines(String textToSplit) { - return Arrays.asList(textToSplit.split("\n")); - } - + private static final long serialVersionUID = 1L; + + // these fields might look unnecessary, but they are needed to support IntelliJ diff viewer + // details: + // https://github.com/JetBrains/intellij-community/commit/b04f577ff6897d011a4dbaa3e381a67d054fb0a2#diff-8ec837b964197c27c51323151bc3eebbR248 + private final String fExpected; + + private final String fActual; + + public FileBasedComparisonFailure( + String expected, String actual, String filenameExpected, String filenameActual) { + super(buildDiff(expected, actual, filenameExpected, filenameActual), expected, actual); + this.fExpected = expected; + this.fActual = actual; + } + + private static String buildDiff( + String expected, String actual, String filenameExpected, String filenameActual) { + List expectedLines = expected == null ? Collections.emptyList() : splitLines(expected); + List actualLines = actual == null ? Collections.emptyList() : splitLines(actual); + + Patch patch = DiffUtils.diff(expectedLines, actualLines); + List> deltas = patch.getDeltas(); + if (deltas.isEmpty()) { + throw new IllegalArgumentException("expected and actual values are equal"); + } + return "\n" + + String.join( + "\n", + DiffUtils.generateUnifiedDiff( + "expected" + (filenameExpected != null ? "/" + filenameExpected : ""), + "actual" + (filenameActual != null ? "/" + filenameActual : ""), + expectedLines, + patch, + 3)); + } + + private static List splitLines(String textToSplit) { + return Arrays.asList(textToSplit.split("\n")); + } } diff --git a/src/main/java/de/cronn/assertions/validationfile/FileExtension.java b/src/main/java/de/cronn/assertions/validationfile/FileExtension.java index cf5040b..b36a452 100644 --- a/src/main/java/de/cronn/assertions/validationfile/FileExtension.java +++ b/src/main/java/de/cronn/assertions/validationfile/FileExtension.java @@ -2,5 +2,5 @@ public interface FileExtension { - String asString(); + String asString(); } diff --git a/src/main/java/de/cronn/assertions/validationfile/FileExtensions.java b/src/main/java/de/cronn/assertions/validationfile/FileExtensions.java index 2593371..4978ea2 100644 --- a/src/main/java/de/cronn/assertions/validationfile/FileExtensions.java +++ b/src/main/java/de/cronn/assertions/validationfile/FileExtensions.java @@ -1,31 +1,31 @@ package de.cronn.assertions.validationfile; public enum FileExtensions implements FileExtension { - TXT("txt"), - XML("xml"), - JSON("json"), - JSON5("json5"), - YAML("yaml"), - DIFF("diff"), - CSV("csv"), - MD("md"), - SQL("sql"), - HTML("html"), - ; + TXT("txt"), + XML("xml"), + JSON("json"), + JSON5("json5"), + YAML("yaml"), + DIFF("diff"), + CSV("csv"), + MD("md"), + SQL("sql"), + HTML("html"), + ; - private final String value; + private final String value; - FileExtensions(String value) { - this.value = value; - } + FileExtensions(String value) { + this.value = value; + } - @Override - public String asString() { - return value; - } + @Override + public String asString() { + return value; + } - @Override - public String toString() { - return value; - } + @Override + public String toString() { + return value; + } } diff --git a/src/main/java/de/cronn/assertions/validationfile/TestData.java b/src/main/java/de/cronn/assertions/validationfile/TestData.java index dfd636a..a44d8d0 100644 --- a/src/main/java/de/cronn/assertions/validationfile/TestData.java +++ b/src/main/java/de/cronn/assertions/validationfile/TestData.java @@ -1,83 +1,79 @@ package de.cronn.assertions.validationfile; +import de.cronn.assertions.validationfile.config.Configuration; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Iterator; import java.util.ServiceLoader; -import de.cronn.assertions.validationfile.config.Configuration; - public final class TestData { - private static final Path TEST_DATA_DIR; - public static final Path TEST_TEMPORARY_DATA_DIR; - public static final Path TEST_VALIDATION_DATA_DIR; - public static final Path TEST_OUTPUT_DATA_DIR; - - static { - Path path = getPath(); - if (path != null) { - TEST_DATA_DIR = path; - } else { - TEST_DATA_DIR = Paths.get("data", "test"); - } - TEST_TEMPORARY_DATA_DIR = TEST_DATA_DIR.resolve(TestDataDir.TMP.getDir()); - TEST_VALIDATION_DATA_DIR = TEST_DATA_DIR.resolve(TestDataDir.VALIDATION.getDir()); - TEST_OUTPUT_DATA_DIR = TEST_DATA_DIR.resolve(TestDataDir.OUTPUT.getDir()); - } - - private static Path getPath() { - ServiceLoader serviceLoader = ServiceLoader.load(Configuration.class); - Iterator iterator = serviceLoader.iterator(); - if (iterator.hasNext()) { - Configuration loader = iterator.next(); - if (iterator.hasNext()) { - throw new IllegalArgumentException("More than one validation files configuration found."); - } - return loader.getDataDirectory(); - } else { - return null; - } - } - - private TestData() { - } - - public enum TestDataDir { - TMP("tmp"), - VALIDATION("validation"), - OUTPUT("output"); - - private final String dir; - - TestDataDir(String dir) { - this.dir = dir; - } - - public String getDir() { - return dir; - } - - } - - public static Path validationFilePath(String fileName) { - assertFilenameHasText(fileName); - return TestData.TEST_VALIDATION_DATA_DIR.resolve(fileName); - } - - public static Path outputPath(String fileName) { - assertFilenameHasText(fileName); - return TestData.TEST_OUTPUT_DATA_DIR.resolve(fileName); - } - - public static Path tmpPath(String fileName) { - assertFilenameHasText(fileName); - return TestData.TEST_TEMPORARY_DATA_DIR.resolve(fileName); - } - - private static void assertFilenameHasText(String fileName) { - if (fileName == null || fileName.isEmpty()) { - throw new IllegalArgumentException("filename must not be blank"); - } - } - + private static final Path TEST_DATA_DIR; + public static final Path TEST_TEMPORARY_DATA_DIR; + public static final Path TEST_VALIDATION_DATA_DIR; + public static final Path TEST_OUTPUT_DATA_DIR; + + static { + Path path = getPath(); + if (path != null) { + TEST_DATA_DIR = path; + } else { + TEST_DATA_DIR = Paths.get("data", "test"); + } + TEST_TEMPORARY_DATA_DIR = TEST_DATA_DIR.resolve(TestDataDir.TMP.getDir()); + TEST_VALIDATION_DATA_DIR = TEST_DATA_DIR.resolve(TestDataDir.VALIDATION.getDir()); + TEST_OUTPUT_DATA_DIR = TEST_DATA_DIR.resolve(TestDataDir.OUTPUT.getDir()); + } + + private static Path getPath() { + ServiceLoader serviceLoader = ServiceLoader.load(Configuration.class); + Iterator iterator = serviceLoader.iterator(); + if (iterator.hasNext()) { + Configuration loader = iterator.next(); + if (iterator.hasNext()) { + throw new IllegalArgumentException("More than one validation files configuration found."); + } + return loader.getDataDirectory(); + } else { + return null; + } + } + + private TestData() {} + + public enum TestDataDir { + TMP("tmp"), + VALIDATION("validation"), + OUTPUT("output"); + + private final String dir; + + TestDataDir(String dir) { + this.dir = dir; + } + + public String getDir() { + return dir; + } + } + + public static Path validationFilePath(String fileName) { + assertFilenameHasText(fileName); + return TestData.TEST_VALIDATION_DATA_DIR.resolve(fileName); + } + + public static Path outputPath(String fileName) { + assertFilenameHasText(fileName); + return TestData.TEST_OUTPUT_DATA_DIR.resolve(fileName); + } + + public static Path tmpPath(String fileName) { + assertFilenameHasText(fileName); + return TestData.TEST_TEMPORARY_DATA_DIR.resolve(fileName); + } + + private static void assertFilenameHasText(String fileName) { + if (fileName == null || fileName.isEmpty()) { + throw new IllegalArgumentException("filename must not be blank"); + } + } } diff --git a/src/main/java/de/cronn/assertions/validationfile/ValidationFileAssertions.java b/src/main/java/de/cronn/assertions/validationfile/ValidationFileAssertions.java index bfc880a..6ddcab3 100644 --- a/src/main/java/de/cronn/assertions/validationfile/ValidationFileAssertions.java +++ b/src/main/java/de/cronn/assertions/validationfile/ValidationFileAssertions.java @@ -1,139 +1,159 @@ package de.cronn.assertions.validationfile; -import java.util.concurrent.Callable; - import de.cronn.assertions.validationfile.normalization.ValidationNormalizer; import de.cronn.assertions.validationfile.util.FileBasedComparisonUtils; +import java.util.concurrent.Callable; public interface ValidationFileAssertions { - String getTestName(); - - default String getValidationFileName(String baseName, FileExtension extension) { - return baseName + "." + extension.asString(); - } - - default String getValidationFileName(String baseName, String suffix, FileExtension extension) { - if (suffix == null || suffix.isEmpty()) { - return getValidationFileName(baseName, extension); - } - return getValidationFileName(baseName + "_" + suffix, extension); - } - - default FailedAssertionHandler failedAssertionHandler() { - return null; - } - - default void assertWithFile(String actualOutput, String filename, ValidationNormalizer normalizer) { - FailedAssertionHandler failedAssertionsCollector = failedAssertionHandler(); - if (failedAssertionsCollector != null) { - failedAssertionsCollector.executeAndHandleFailedAssertion(() -> { - FileBasedComparisonUtils.compareActualWithFileHidden(actualOutput, filename, normalizer); - return null; - }); - } else { - FileBasedComparisonUtils.compareActualWithFileHidden(actualOutput, filename, normalizer); - } - } - - default void assertWithFile(String actualString, FileExtension extension) { - assertWithFile(actualString, getValidationFileName(getTestName(), extension), null); - } - - default void assertWithFileWithSuffix(String actualString, String suffix, FileExtension extension) { - assertWithFileWithSuffix(actualString, null, suffix, extension); - } - - default void assertWithFileWithSuffix(String actualString, ValidationNormalizer validationNormalizer, String suffix, FileExtension extension) { - assertWithFile(actualString, getValidationFileName(getTestName(), suffix, extension), validationNormalizer); - } - - default void assertWithFile(String actualOutput, ValidationNormalizer validationNormalizer, FileExtension extension) { - assertWithFile(actualOutput, getValidationFileName(getTestName(), extension), validationNormalizer); - } - - default void assertWithFile(String actualString) { - assertWithFile(actualString, FileExtensions.TXT); - } - - default void assertWithFileWithSuffix(String actualString, String suffix) { - assertWithFileWithSuffix(actualString, suffix, FileExtensions.TXT); - } - - default void assertWithFileWithSuffix(String actualString, ValidationNormalizer validationNormalizer, String suffix) { - assertWithFileWithSuffix(actualString, validationNormalizer, suffix, FileExtensions.TXT); - } - - default void assertWithFile(String actualOutput, ValidationNormalizer validationNormalizer) { - assertWithFile(actualOutput, validationNormalizer, FileExtensions.TXT); - } - - default void assertWithJsonFile(String actualJsonString) { - assertWithFile(actualJsonString, FileExtensions.JSON); - } - - default void assertWithJsonFileWithSuffix(String actualJsonString, String suffix) { - assertWithFileWithSuffix(actualJsonString, suffix, FileExtensions.JSON); - } - - default void assertWithJsonFileWithSuffix(String actualJsonString, ValidationNormalizer validationNormalizer, String suffix) { - assertWithFileWithSuffix(actualJsonString, validationNormalizer, suffix, FileExtensions.JSON); - } - - default void assertWithJsonFile(String actualJsonString, ValidationNormalizer validationNormalizer) { - assertWithFile(actualJsonString, validationNormalizer, FileExtensions.JSON); - } - - default void assertWithJson5File(String actualJsonString) { - assertWithFile(actualJsonString, FileExtensions.JSON5); - } - - default void assertWithJson5FileWithSuffix(String actualJsonString, String suffix) { - assertWithFileWithSuffix(actualJsonString, suffix, FileExtensions.JSON5); - } - - default void assertWithJson5FileWithSuffix(String actualJsonString, ValidationNormalizer validationNormalizer, String suffix) { - assertWithFileWithSuffix(actualJsonString, validationNormalizer, suffix, FileExtensions.JSON5); - } - - default void assertWithJson5File(String actualJsonString, ValidationNormalizer validationNormalizer) { - assertWithFile(actualJsonString, validationNormalizer, FileExtensions.JSON5); - } - - default void assertWithYamlFile(String actualJsonString) { - assertWithFile(actualJsonString, FileExtensions.YAML); - } - - default void assertWithYamlFileWithSuffix(String actualJsonString, String suffix) { - assertWithFileWithSuffix(actualJsonString, suffix, FileExtensions.YAML); - } - - default void assertWithYamlFileWithSuffix(String actualJsonString, ValidationNormalizer validationNormalizer, String suffix) { - assertWithFileWithSuffix(actualJsonString, validationNormalizer, suffix, FileExtensions.YAML); - } - - default void assertWithYamlFile(String actualJsonString, ValidationNormalizer validationNormalizer) { - assertWithFile(actualJsonString, validationNormalizer, FileExtensions.YAML); - } - - default void assertWithXmlFile(String actualXmlString) { - assertWithFile(actualXmlString, FileExtensions.XML); - } - - default void assertWithXmlFileWithSuffix(String actualXmlString, String suffix) { - assertWithFileWithSuffix(actualXmlString, suffix, FileExtensions.XML); - } - - default void assertWithXmlFileWithSuffix(String actualXmlString, ValidationNormalizer validationNormalizer, String suffix) { - assertWithFileWithSuffix(actualXmlString, validationNormalizer, suffix, FileExtensions.XML); - } - - default void assertWithXmlFile(String actualXmlString, ValidationNormalizer validationNormalizer) { - assertWithFile(actualXmlString, validationNormalizer, FileExtensions.XML); - } - - interface FailedAssertionHandler { - void executeAndHandleFailedAssertion(Callable callable); - } - + String getTestName(); + + default String getValidationFileName(String baseName, FileExtension extension) { + return baseName + "." + extension.asString(); + } + + default String getValidationFileName(String baseName, String suffix, FileExtension extension) { + if (suffix == null || suffix.isEmpty()) { + return getValidationFileName(baseName, extension); + } + return getValidationFileName(baseName + "_" + suffix, extension); + } + + default FailedAssertionHandler failedAssertionHandler() { + return null; + } + + default void assertWithFile( + String actualOutput, String filename, ValidationNormalizer normalizer) { + FailedAssertionHandler failedAssertionsCollector = failedAssertionHandler(); + if (failedAssertionsCollector != null) { + failedAssertionsCollector.executeAndHandleFailedAssertion( + () -> { + FileBasedComparisonUtils.compareActualWithFileHidden( + actualOutput, filename, normalizer); + return null; + }); + } else { + FileBasedComparisonUtils.compareActualWithFileHidden(actualOutput, filename, normalizer); + } + } + + default void assertWithFile(String actualString, FileExtension extension) { + assertWithFile(actualString, getValidationFileName(getTestName(), extension), null); + } + + default void assertWithFileWithSuffix( + String actualString, String suffix, FileExtension extension) { + assertWithFileWithSuffix(actualString, null, suffix, extension); + } + + default void assertWithFileWithSuffix( + String actualString, + ValidationNormalizer validationNormalizer, + String suffix, + FileExtension extension) { + assertWithFile( + actualString, + getValidationFileName(getTestName(), suffix, extension), + validationNormalizer); + } + + default void assertWithFile( + String actualOutput, ValidationNormalizer validationNormalizer, FileExtension extension) { + assertWithFile( + actualOutput, getValidationFileName(getTestName(), extension), validationNormalizer); + } + + default void assertWithFile(String actualString) { + assertWithFile(actualString, FileExtensions.TXT); + } + + default void assertWithFileWithSuffix(String actualString, String suffix) { + assertWithFileWithSuffix(actualString, suffix, FileExtensions.TXT); + } + + default void assertWithFileWithSuffix( + String actualString, ValidationNormalizer validationNormalizer, String suffix) { + assertWithFileWithSuffix(actualString, validationNormalizer, suffix, FileExtensions.TXT); + } + + default void assertWithFile(String actualOutput, ValidationNormalizer validationNormalizer) { + assertWithFile(actualOutput, validationNormalizer, FileExtensions.TXT); + } + + default void assertWithJsonFile(String actualJsonString) { + assertWithFile(actualJsonString, FileExtensions.JSON); + } + + default void assertWithJsonFileWithSuffix(String actualJsonString, String suffix) { + assertWithFileWithSuffix(actualJsonString, suffix, FileExtensions.JSON); + } + + default void assertWithJsonFileWithSuffix( + String actualJsonString, ValidationNormalizer validationNormalizer, String suffix) { + assertWithFileWithSuffix(actualJsonString, validationNormalizer, suffix, FileExtensions.JSON); + } + + default void assertWithJsonFile( + String actualJsonString, ValidationNormalizer validationNormalizer) { + assertWithFile(actualJsonString, validationNormalizer, FileExtensions.JSON); + } + + default void assertWithJson5File(String actualJsonString) { + assertWithFile(actualJsonString, FileExtensions.JSON5); + } + + default void assertWithJson5FileWithSuffix(String actualJsonString, String suffix) { + assertWithFileWithSuffix(actualJsonString, suffix, FileExtensions.JSON5); + } + + default void assertWithJson5FileWithSuffix( + String actualJsonString, ValidationNormalizer validationNormalizer, String suffix) { + assertWithFileWithSuffix(actualJsonString, validationNormalizer, suffix, FileExtensions.JSON5); + } + + default void assertWithJson5File( + String actualJsonString, ValidationNormalizer validationNormalizer) { + assertWithFile(actualJsonString, validationNormalizer, FileExtensions.JSON5); + } + + default void assertWithYamlFile(String actualJsonString) { + assertWithFile(actualJsonString, FileExtensions.YAML); + } + + default void assertWithYamlFileWithSuffix(String actualJsonString, String suffix) { + assertWithFileWithSuffix(actualJsonString, suffix, FileExtensions.YAML); + } + + default void assertWithYamlFileWithSuffix( + String actualJsonString, ValidationNormalizer validationNormalizer, String suffix) { + assertWithFileWithSuffix(actualJsonString, validationNormalizer, suffix, FileExtensions.YAML); + } + + default void assertWithYamlFile( + String actualJsonString, ValidationNormalizer validationNormalizer) { + assertWithFile(actualJsonString, validationNormalizer, FileExtensions.YAML); + } + + default void assertWithXmlFile(String actualXmlString) { + assertWithFile(actualXmlString, FileExtensions.XML); + } + + default void assertWithXmlFileWithSuffix(String actualXmlString, String suffix) { + assertWithFileWithSuffix(actualXmlString, suffix, FileExtensions.XML); + } + + default void assertWithXmlFileWithSuffix( + String actualXmlString, ValidationNormalizer validationNormalizer, String suffix) { + assertWithFileWithSuffix(actualXmlString, validationNormalizer, suffix, FileExtensions.XML); + } + + default void assertWithXmlFile( + String actualXmlString, ValidationNormalizer validationNormalizer) { + assertWithFile(actualXmlString, validationNormalizer, FileExtensions.XML); + } + + interface FailedAssertionHandler { + void executeAndHandleFailedAssertion(Callable callable); + } } diff --git a/src/main/java/de/cronn/assertions/validationfile/config/Configuration.java b/src/main/java/de/cronn/assertions/validationfile/config/Configuration.java index 05eba4b..90402b6 100644 --- a/src/main/java/de/cronn/assertions/validationfile/config/Configuration.java +++ b/src/main/java/de/cronn/assertions/validationfile/config/Configuration.java @@ -4,6 +4,5 @@ public interface Configuration { - Path getDataDirectory(); - + Path getDataDirectory(); } diff --git a/src/main/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertions.java b/src/main/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertions.java index cf8f960..2a63a23 100644 --- a/src/main/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertions.java +++ b/src/main/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertions.java @@ -1,26 +1,25 @@ package de.cronn.assertions.validationfile.junit5; +import de.cronn.assertions.validationfile.ValidationFileAssertions; +import de.cronn.assertions.validationfile.util.TestNameUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; -import de.cronn.assertions.validationfile.ValidationFileAssertions; -import de.cronn.assertions.validationfile.util.TestNameUtils; - /** - *

* Provides test name as default name of validation file.
* This interface is an alternative for storing {@link TestInfo} in class field. * - *

- * Notes: + *

Notes: + * *

    - *
  • Parallel test execution ExecutionMode.CONCURRENT combined with TestInstance.Lifecycle.PER_CLASS is not supported.
  • + *
  • Parallel test execution ExecutionMode.CONCURRENT combined with + * TestInstance.Lifecycle.PER_CLASS is not supported. *
* - *

- * Example:
+ *

Example:
* MyTestClass.java: + * *


  * class MyTestClass implements JUnit5ValidationFileAssertions {
  *   @Test
@@ -32,21 +31,20 @@
  */
 public interface JUnit5ValidationFileAssertions extends ValidationFileAssertions {
 
-	@BeforeEach
-	default void storeTestInfoOfTestBeingExecuted(TestInfo testInfo) {
-		TestInfoStore.store(this, testInfo);
-	}
-
-	@AfterEach
-	default void discardTestInfoOfTestBeingExecuted() {
-		TestInfoStore.discard(this);
-	}
+  @BeforeEach
+  default void storeTestInfoOfTestBeingExecuted(TestInfo testInfo) {
+    TestInfoStore.store(this, testInfo);
+  }
 
-	@Override
-	default String getTestName() {
-		Class testClass = TestInfoStore.getTestClass(this);
-		String testMethodName = TestInfoStore.getTestMethodName(this);
-		return TestNameUtils.getTestName(testClass, testMethodName);
-	}
+  @AfterEach
+  default void discardTestInfoOfTestBeingExecuted() {
+    TestInfoStore.discard(this);
+  }
 
+  @Override
+  default String getTestName() {
+    Class testClass = TestInfoStore.getTestClass(this);
+    String testMethodName = TestInfoStore.getTestMethodName(this);
+    return TestNameUtils.getTestName(testClass, testMethodName);
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/junit5/TestInfoStore.java b/src/main/java/de/cronn/assertions/validationfile/junit5/TestInfoStore.java
index 68dfc35..f0b6d03 100644
--- a/src/main/java/de/cronn/assertions/validationfile/junit5/TestInfoStore.java
+++ b/src/main/java/de/cronn/assertions/validationfile/junit5/TestInfoStore.java
@@ -3,41 +3,41 @@
 import java.lang.reflect.Method;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-
 import org.junit.jupiter.api.TestInfo;
 
 final class TestInfoStore {
 
-	private static final Map testInfos = new ConcurrentHashMap<>();
+  private static final Map testInfos = new ConcurrentHashMap<>();
 
-	private TestInfoStore() {
-	}
+  private TestInfoStore() {}
 
-	static void store(Object testInstance, TestInfo testInfo) {
-		Class testClass = testInfo.getTestClass().orElseThrow(() -> new IllegalStateException("No test class"));
-		Method testMethod = testInfo.getTestMethod().orElseThrow(() -> new IllegalStateException("No test method"));
-		testInfos.put(testInstance, new SimpleTestInfo(testClass, testMethod.getName()));
-	}
+  static void store(Object testInstance, TestInfo testInfo) {
+    Class testClass =
+        testInfo.getTestClass().orElseThrow(() -> new IllegalStateException("No test class"));
+    Method testMethod =
+        testInfo.getTestMethod().orElseThrow(() -> new IllegalStateException("No test method"));
+    testInfos.put(testInstance, new SimpleTestInfo(testClass, testMethod.getName()));
+  }
 
-	static void discard(Object testInstance) {
-		testInfos.remove(testInstance);
-	}
+  static void discard(Object testInstance) {
+    testInfos.remove(testInstance);
+  }
 
-	static Class getTestClass(Object testInstance) {
-		return testInfos.get(testInstance).testClass;
-	}
+  static Class getTestClass(Object testInstance) {
+    return testInfos.get(testInstance).testClass;
+  }
 
-	static String getTestMethodName(Object testInstance) {
-		return testInfos.get(testInstance).methodName;
-	}
+  static String getTestMethodName(Object testInstance) {
+    return testInfos.get(testInstance).methodName;
+  }
 
-	private static final class SimpleTestInfo {
-		final Class testClass;
-		final String methodName;
+  private static final class SimpleTestInfo {
+    final Class testClass;
+    final String methodName;
 
-		private SimpleTestInfo(Class testClass, String methodName) {
-			this.testClass = testClass;
-			this.methodName = methodName;
-		}
-	}
+    private SimpleTestInfo(Class testClass, String methodName) {
+      this.testClass = testClass;
+      this.methodName = methodName;
+    }
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/CollectionOfStringsValidationNormalizer.java b/src/main/java/de/cronn/assertions/validationfile/normalization/CollectionOfStringsValidationNormalizer.java
index b9eef34..d454b18 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/CollectionOfStringsValidationNormalizer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/CollectionOfStringsValidationNormalizer.java
@@ -4,20 +4,21 @@
 
 public class CollectionOfStringsValidationNormalizer implements ValidationNormalizer {
 
-	private final Collection inputStrings;
-	private final String outputString;
+  private final Collection inputStrings;
+  private final String outputString;
 
-	public CollectionOfStringsValidationNormalizer(Collection inputStrings, String outputString) {
-		this.inputStrings = inputStrings;
-		this.outputString = outputString;
-	}
+  public CollectionOfStringsValidationNormalizer(
+      Collection inputStrings, String outputString) {
+    this.inputStrings = inputStrings;
+    this.outputString = outputString;
+  }
 
-	@Override
-	public String normalize(String source) {
-		String output = source;
-		for (String inputString : inputStrings) {
-			output = output.replaceAll(inputString, outputString);
-		}
-		return output;
-	}
+  @Override
+  public String normalize(String source) {
+    String output = source;
+    for (String inputString : inputStrings) {
+      output = output.replaceAll(inputString, outputString);
+    }
+    return output;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/ConstantIdProvider.java b/src/main/java/de/cronn/assertions/validationfile/normalization/ConstantIdProvider.java
index 5f5b7b6..5d37e07 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/ConstantIdProvider.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/ConstantIdProvider.java
@@ -2,15 +2,14 @@
 
 public class ConstantIdProvider implements IdProvider {
 
-	private long value;
+  private long value;
 
-	public ConstantIdProvider(long value) {
-		this.value = value;
-	}
-
-	@Override
-	public long next() {
-		return value;
-	}
+  public ConstantIdProvider(long value) {
+    this.value = value;
+  }
 
+  @Override
+  public long next() {
+    return value;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/IdNormalizer.java b/src/main/java/de/cronn/assertions/validationfile/normalization/IdNormalizer.java
index e226e74..9e7db43 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/IdNormalizer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/IdNormalizer.java
@@ -7,81 +7,78 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-
 public class IdNormalizer implements ValidationNormalizer {
 
-	private static final String BACKSLASH = "\\\\";
-
-	/**
-	 * non-empty, backslash-escaped (like JSON)
-	 */
-	public static final String ESCAPED_STRING = "(?:[^" + BACKSLASH + "]|" + BACKSLASH + ".)+?";
+  private static final String BACKSLASH = "\\\\";
 
-	private final boolean doIdMapping;
-	private final Collection patterns;
-	private final Map idMap = new HashMap<>();
-	private final IdProvider idProvider;
-	private final String idPrefix;
+  /** non-empty, backslash-escaped (like JSON) */
+  public static final String ESCAPED_STRING = "(?:[^" + BACKSLASH + "]|" + BACKSLASH + ".)+?";
 
-	public IdNormalizer(String... regexp) {
-		this(new IncrementingIdProvider(), "", true, regexp);
-	}
+  private final boolean doIdMapping;
+  private final Collection patterns;
+  private final Map idMap = new HashMap<>();
+  private final IdProvider idProvider;
+  private final String idPrefix;
 
-	public IdNormalizer(IdProvider idProvider, String idPrefix, String... regularExpressions) {
-		this(idProvider, idPrefix, true, regularExpressions);
-	}
+  public IdNormalizer(String... regexp) {
+    this(new IncrementingIdProvider(), "", true, regexp);
+  }
 
-	public IdNormalizer(IdProvider idProvider, String idPrefix, boolean doIdMapping, String... regularExpressions) {
-		this.doIdMapping = doIdMapping;
-		this.idProvider = idProvider;
-		this.idPrefix = idPrefix;
-		this.patterns = new ArrayList<>();
-		for (String regularExpression : regularExpressions) {
-			Pattern pattern = Pattern.compile(regularExpression);
-			this.patterns.add(pattern);
-		}
-	}
+  public IdNormalizer(IdProvider idProvider, String idPrefix, String... regularExpressions) {
+    this(idProvider, idPrefix, true, regularExpressions);
+  }
 
-	@Override
-	public String normalize(String input) {
-		String result = input;
-		for (Pattern pattern : patterns) {
-			result = normalizeOnePattern(result, pattern);
-		}
-		return result;
-	}
+  public IdNormalizer(
+      IdProvider idProvider, String idPrefix, boolean doIdMapping, String... regularExpressions) {
+    this.doIdMapping = doIdMapping;
+    this.idProvider = idProvider;
+    this.idPrefix = idPrefix;
+    this.patterns = new ArrayList<>();
+    for (String regularExpression : regularExpressions) {
+      Pattern pattern = Pattern.compile(regularExpression);
+      this.patterns.add(pattern);
+    }
+  }
 
-	private String normalizeOnePattern(String input, Pattern pattern) {
-		Matcher m = pattern.matcher(input);
-		StringBuffer result = new StringBuffer();
-		while (m.find()) {
-			String id = m.group(1);
-			String newIdAsString = lookupNewIdAsString(id);
-			String stringBeforeGroup = input.substring(m.start(), m.start(1));
-			String stringAfterGroup = input.substring(m.end(1), m.end());
-			String replacement = stringBeforeGroup + idPrefix + newIdAsString + stringAfterGroup;
-			String escapedReplacement = replacement.replace("\\", "\\\\");
-			escapedReplacement = escapedReplacement.replace("$", "\\$");
-			m.appendReplacement(result, escapedReplacement);
-		}
-		m.appendTail(result);
+  @Override
+  public String normalize(String input) {
+    String result = input;
+    for (Pattern pattern : patterns) {
+      result = normalizeOnePattern(result, pattern);
+    }
+    return result;
+  }
 
-		return result.toString();
-	}
+  private String normalizeOnePattern(String input, Pattern pattern) {
+    Matcher m = pattern.matcher(input);
+    StringBuffer result = new StringBuffer();
+    while (m.find()) {
+      String id = m.group(1);
+      String newIdAsString = lookupNewIdAsString(id);
+      String stringBeforeGroup = input.substring(m.start(), m.start(1));
+      String stringAfterGroup = input.substring(m.end(1), m.end());
+      String replacement = stringBeforeGroup + idPrefix + newIdAsString + stringAfterGroup;
+      String escapedReplacement = replacement.replace("\\", "\\\\");
+      escapedReplacement = escapedReplacement.replace("$", "\\$");
+      m.appendReplacement(result, escapedReplacement);
+    }
+    m.appendTail(result);
 
-	private String lookupNewIdAsString(String originalId) {
-		String newIdAsString = null;
-		if (doIdMapping) {
-			newIdAsString = idMap.get(originalId);
-		}
-		if (newIdAsString == null) {
-			long newId = idProvider.next();
-			newIdAsString = String.valueOf(newId);
-			if (doIdMapping) {
-				idMap.put(originalId, newIdAsString);
-			}
-		}
-		return newIdAsString;
-	}
+    return result.toString();
+  }
 
+  private String lookupNewIdAsString(String originalId) {
+    String newIdAsString = null;
+    if (doIdMapping) {
+      newIdAsString = idMap.get(originalId);
+    }
+    if (newIdAsString == null) {
+      long newId = idProvider.next();
+      newIdAsString = String.valueOf(newId);
+      if (doIdMapping) {
+        idMap.put(originalId, newIdAsString);
+      }
+    }
+    return newIdAsString;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/IdProvider.java b/src/main/java/de/cronn/assertions/validationfile/normalization/IdProvider.java
index 8cd0bde..f1a72b7 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/IdProvider.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/IdProvider.java
@@ -2,5 +2,5 @@
 
 public interface IdProvider {
 
-	long next();
+  long next();
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/IncrementingIdProvider.java b/src/main/java/de/cronn/assertions/validationfile/normalization/IncrementingIdProvider.java
index e2a7fc0..ef694dd 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/IncrementingIdProvider.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/IncrementingIdProvider.java
@@ -2,19 +2,18 @@
 
 public class IncrementingIdProvider implements IdProvider {
 
-	private long current;
+  private long current;
 
-	public IncrementingIdProvider() {
-		reset();
-	}
+  public IncrementingIdProvider() {
+    reset();
+  }
 
-	public void reset() {
-		current = 0;
-	}
-
-	@Override
-	public long next() {
-		return ++current;
-	}
+  public void reset() {
+    current = 0;
+  }
 
+  @Override
+  public long next() {
+    return ++current;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/SimpleRegexReplacement.java b/src/main/java/de/cronn/assertions/validationfile/normalization/SimpleRegexReplacement.java
index f60f5ba..df1839f 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/SimpleRegexReplacement.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/SimpleRegexReplacement.java
@@ -4,43 +4,42 @@
 
 public class SimpleRegexReplacement implements ValidationNormalizer {
 
-	private String regex;
+  private String regex;
 
-	private String replacement;
+  private String replacement;
 
-	public SimpleRegexReplacement(String regex, String replacement) {
-		Objects.requireNonNull(regex);
-		this.regex = regex;
-		this.replacement = replacement;
-	}
+  public SimpleRegexReplacement(String regex, String replacement) {
+    Objects.requireNonNull(regex);
+    this.regex = regex;
+    this.replacement = replacement;
+  }
 
-	public SimpleRegexReplacement(String regex) {
-		this(regex, "[masked]");
-	}
+  public SimpleRegexReplacement(String regex) {
+    this(regex, "[masked]");
+  }
 
-	public String apply(String source) {
-		return source.replaceAll(regex, replacement);
-	}
+  public String apply(String source) {
+    return source.replaceAll(regex, replacement);
+  }
 
-	public String getRegex() {
-		return regex;
-	}
+  public String getRegex() {
+    return regex;
+  }
 
-	public String getReplacement() {
-		return replacement;
-	}
+  public String getReplacement() {
+    return replacement;
+  }
 
-	@Override
-	public String normalize(String source) {
-		return apply(source);
-	}
+  @Override
+  public String normalize(String source) {
+    return apply(source);
+  }
 
-	public void setRegex(String regex) {
-		this.regex = regex;
-	}
-
-	public void setReplacement(String replacement) {
-		this.replacement = replacement;
-	}
+  public void setRegex(String regex) {
+    this.regex = regex;
+  }
 
+  public void setReplacement(String replacement) {
+    this.replacement = replacement;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/StringNormalizer.java b/src/main/java/de/cronn/assertions/validationfile/normalization/StringNormalizer.java
index 05074d2..d983f54 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/StringNormalizer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/StringNormalizer.java
@@ -2,15 +2,13 @@
 
 public final class StringNormalizer {
 
-	private StringNormalizer() {
-	}
+  private StringNormalizer() {}
 
-	public static String normalizeLineEndings(CharSequence string) {
-		return normalizeLineEndingsAndTrim(string) + "\n";
-	}
-
-	public static String normalizeLineEndingsAndTrim(CharSequence string) {
-		return string.toString().replace("\r\n", "\n").trim();
-	}
+  public static String normalizeLineEndings(CharSequence string) {
+    return normalizeLineEndingsAndTrim(string) + "\n";
+  }
 
+  public static String normalizeLineEndingsAndTrim(CharSequence string) {
+    return string.toString().replace("\r\n", "\n").trim();
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java b/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java
index 0a75d56..6751c25 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/UuidNormalizer.java
@@ -2,15 +2,14 @@
 
 public class UuidNormalizer implements ValidationNormalizer {
 
-	private final ValidationNormalizer delegate =
-		new IdNormalizer(
-			new IncrementingIdProvider(),
-			"UUID_",
-			"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})");
-
-	@Override
-	public String normalize(String source) {
-		return delegate.normalize(source);
-	}
-
+  private final ValidationNormalizer delegate =
+      new IdNormalizer(
+          new IncrementingIdProvider(),
+          "UUID_",
+          "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})");
+
+  @Override
+  public String normalize(String source) {
+    return delegate.normalize(source);
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/normalization/ValidationNormalizer.java b/src/main/java/de/cronn/assertions/validationfile/normalization/ValidationNormalizer.java
index b5cadb7..9fcaca5 100644
--- a/src/main/java/de/cronn/assertions/validationfile/normalization/ValidationNormalizer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/normalization/ValidationNormalizer.java
@@ -6,30 +6,29 @@
 @FunctionalInterface
 public interface ValidationNormalizer {
 
-	String normalize(String source);
-
-	default ValidationNormalizer and(ValidationNormalizer otherNormalizer) {
-		return ValidationNormalizer.combine(this, otherNormalizer);
-	}
-
-	static ValidationNormalizer doNothing() {
-		return s -> s;
-	}
-
-	static ValidationNormalizer combine(ValidationNormalizer... normalizers) {
-		return combine(Arrays.asList(normalizers));
-	}
-
-	static ValidationNormalizer combine(Collection normalizers) {
-		return s -> applyNormalizers(s, normalizers);
-	}
-
-	static String applyNormalizers(String input, Collection normalizers) {
-		String normalized = input;
-		for (ValidationNormalizer validationNormalizer : normalizers) {
-			normalized = validationNormalizer.normalize(normalized);
-		}
-		return normalized;
-	}
-
+  String normalize(String source);
+
+  default ValidationNormalizer and(ValidationNormalizer otherNormalizer) {
+    return ValidationNormalizer.combine(this, otherNormalizer);
+  }
+
+  static ValidationNormalizer doNothing() {
+    return s -> s;
+  }
+
+  static ValidationNormalizer combine(ValidationNormalizer... normalizers) {
+    return combine(Arrays.asList(normalizers));
+  }
+
+  static ValidationNormalizer combine(Collection normalizers) {
+    return s -> applyNormalizers(s, normalizers);
+  }
+
+  static String applyNormalizers(String input, Collection normalizers) {
+    String normalized = input;
+    for (ValidationNormalizer validationNormalizer : normalizers) {
+      normalized = validationNormalizer.normalize(normalized);
+    }
+    return normalized;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractJsonReplacer.java b/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractJsonReplacer.java
index 8c13517..47bdf87 100644
--- a/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractJsonReplacer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractJsonReplacer.java
@@ -4,16 +4,15 @@
 
 public abstract class AbstractJsonReplacer> {
 
-	protected static final String COLON_WITH_WHITESPACES_GROUP = "(\\s?:\\s?)";
-	protected String key;
+  protected static final String COLON_WITH_WHITESPACES_GROUP = "(\\s?:\\s?)";
+  protected String key;
 
-	protected abstract T getThis();
+  protected abstract T getThis();
 
-	public abstract ValidationNormalizer build();
-
-	public T withKey(String key) {
-		this.key = key;
-		return getThis();
-	}
+  public abstract ValidationNormalizer build();
 
+  public T withKey(String key) {
+    this.key = key;
+    return getThis();
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilder.java b/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilder.java
index bde1b53..a0d9503 100644
--- a/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilder.java
+++ b/src/main/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilder.java
@@ -1,86 +1,84 @@
 package de.cronn.assertions.validationfile.replacements;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 public abstract class AbstractXmlReplacerBuilder> {
 
-	private String namespace;
-	private String elementName;
-	private final Map attributes = new LinkedHashMap<>();
-
-	protected abstract T getThis();
+  private String namespace;
+  private String elementName;
+  private final Map attributes = new LinkedHashMap<>();
 
-	protected abstract ValidationNormalizer build(String startTag, String endTag);
+  protected abstract T getThis();
 
-	public T withNamespace(String namespace) {
-		this.namespace = namespace;
-		return getThis();
-	}
+  protected abstract ValidationNormalizer build(String startTag, String endTag);
 
-	public T withElementName(String attribute) {
-		this.elementName = attribute;
-		return getThis();
-	}
+  public T withNamespace(String namespace) {
+    this.namespace = namespace;
+    return getThis();
+  }
 
-	public T withAttribute(String name, String value) {
-		attributes.put(name, value);
+  public T withElementName(String attribute) {
+    this.elementName = attribute;
+    return getThis();
+  }
 
-		return getThis();
-	}
+  public T withAttribute(String name, String value) {
+    attributes.put(name, value);
 
-	String createEndTag() {
-		StringBuilder stringBuilder = new StringBuilder();
+    return getThis();
+  }
 
-		stringBuilder.append("");
+    stringBuilder.append(elementName);
 
-		return stringBuilder.toString();
-	}
+    stringBuilder.append(">");
 
-	String createStartTag() {
-		StringBuilder stringBuilder = new StringBuilder();
+    return stringBuilder.toString();
+  }
 
-		stringBuilder.append("<");
-		if (namespace != null) {
-			stringBuilder.append(namespace).append(":");
-		}
+  String createStartTag() {
+    StringBuilder stringBuilder = new StringBuilder();
 
-		stringBuilder.append(elementName);
-		for (Map.Entry entry : attributes.entrySet()) {
-			String attributeName = entry.getKey();
-			stringBuilder.append(" ");
-			stringBuilder.append(attributeName);
+    stringBuilder.append("<");
+    if (namespace != null) {
+      stringBuilder.append(namespace).append(":");
+    }
 
-			String attributeValue = entry.getValue();
-			if (attributeValue != null) {
-				stringBuilder.append("=\"");
-				stringBuilder.append(attributeValue);
-				stringBuilder.append("\"");
-			}
-		}
+    stringBuilder.append(elementName);
+    for (Map.Entry entry : attributes.entrySet()) {
+      String attributeName = entry.getKey();
+      stringBuilder.append(" ");
+      stringBuilder.append(attributeName);
 
-		stringBuilder.append(">");
+      String attributeValue = entry.getValue();
+      if (attributeValue != null) {
+        stringBuilder.append("=\"");
+        stringBuilder.append(attributeValue);
+        stringBuilder.append("\"");
+      }
+    }
 
-		return stringBuilder.toString();
-	}
+    stringBuilder.append(">");
 
-	public final ValidationNormalizer build() {
-		Objects.requireNonNull(elementName);
+    return stringBuilder.toString();
+  }
 
-		String startTag = createStartTag();
-		String endTag = createEndTag();
+  public final ValidationNormalizer build() {
+    Objects.requireNonNull(elementName);
 
-		return build(startTag, endTag);
-	}
+    String startTag = createStartTag();
+    String endTag = createEndTag();
 
+    return build(startTag, endTag);
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java b/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java
index aca02d7..4a6f75d 100644
--- a/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java
+++ b/src/main/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacer.java
@@ -1,81 +1,83 @@
 package de.cronn.assertions.validationfile.replacements;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.TemporalAccessor;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 public class DateTimeReplacer implements ValidationNormalizer {
-	private static final String DATE_TIME_GROUP_NAME = "DateTime";
-
-	private final Pattern pattern;
-	private final DateTimeFormatter sourceFormat;
-	private final DateTimeFormatter destinationFormat;
-
-	public DateTimeReplacer(Pattern pattern, DateTimeFormatter sourceFormat, DateTimeFormatter destinationFormat) {
-		this.pattern = pattern;
-		this.sourceFormat = sourceFormat;
-		this.destinationFormat = destinationFormat;
-	}
-
-	@Override
-	public String normalize(String textToNormalize) {
-		StringBuilder normalizedResultBuilder = new StringBuilder();
-
-		Matcher matcher = pattern.matcher(textToNormalize);
-		int endOfLastMatchIndex = 0;
-
-		boolean matchFound = false;
-
-		while (matcher.find()) {
-			matchFound = true;
-
-			int startOfCurrentMatchIndex = matcher.start();
-			int endOfCurrentMatchIndex = matcher.end();
-
-			appendFromSourceToResult(textToNormalize, normalizedResultBuilder, endOfLastMatchIndex, startOfCurrentMatchIndex);
-			String match = textToNormalize.substring(startOfCurrentMatchIndex, endOfCurrentMatchIndex);
-
-			try {
-				String possiblyDateTime = matchDateTime(textToNormalize, matcher);
-				String formattedDateTime = matchDateTimeAndConvertFromSourceToDestinationFormat(possiblyDateTime);
-				normalizedResultBuilder.append(match.replace(possiblyDateTime, formattedDateTime));
-			} catch (DateTimeParseException e) {
-				normalizedResultBuilder.append(match);
-			}
-
-			endOfLastMatchIndex = endOfCurrentMatchIndex;
-		}
-
-		if (!matchFound) {
-			return textToNormalize;
-		}
-
-		appendFromSourceToResult(textToNormalize, normalizedResultBuilder,
-			endOfLastMatchIndex, textToNormalize.length());
-
-		return normalizedResultBuilder.toString();
-	}
-
-	private String matchDateTimeAndConvertFromSourceToDestinationFormat(String possiblyDateTime) {
-		TemporalAccessor parsedDateTime = sourceFormat.parse(possiblyDateTime);
-		return destinationFormat.format(parsedDateTime);
-	}
-
-	private String matchDateTime(String text, Matcher matcher) {
-		return text.substring(matcher.start(DATE_TIME_GROUP_NAME), matcher.end(DATE_TIME_GROUP_NAME));
-	}
-
-	private void appendFromSourceToResult(String source, StringBuilder resultBuilder, int fromIndex, int toIndex) {
-		resultBuilder.append(source, fromIndex, toIndex);
-	}
-
-	@Override
-	public String toString() {
-		return "DateTimeReplacer for pattern " + pattern.toString() + ".";
-	}
-
+  private static final String DATE_TIME_GROUP_NAME = "DateTime";
+
+  private final Pattern pattern;
+  private final DateTimeFormatter sourceFormat;
+  private final DateTimeFormatter destinationFormat;
+
+  public DateTimeReplacer(
+      Pattern pattern, DateTimeFormatter sourceFormat, DateTimeFormatter destinationFormat) {
+    this.pattern = pattern;
+    this.sourceFormat = sourceFormat;
+    this.destinationFormat = destinationFormat;
+  }
+
+  @Override
+  public String normalize(String textToNormalize) {
+    StringBuilder normalizedResultBuilder = new StringBuilder();
+
+    Matcher matcher = pattern.matcher(textToNormalize);
+    int endOfLastMatchIndex = 0;
+
+    boolean matchFound = false;
+
+    while (matcher.find()) {
+      matchFound = true;
+
+      int startOfCurrentMatchIndex = matcher.start();
+      int endOfCurrentMatchIndex = matcher.end();
+
+      appendFromSourceToResult(
+          textToNormalize, normalizedResultBuilder, endOfLastMatchIndex, startOfCurrentMatchIndex);
+      String match = textToNormalize.substring(startOfCurrentMatchIndex, endOfCurrentMatchIndex);
+
+      try {
+        String possiblyDateTime = matchDateTime(textToNormalize, matcher);
+        String formattedDateTime =
+            matchDateTimeAndConvertFromSourceToDestinationFormat(possiblyDateTime);
+        normalizedResultBuilder.append(match.replace(possiblyDateTime, formattedDateTime));
+      } catch (DateTimeParseException e) {
+        normalizedResultBuilder.append(match);
+      }
+
+      endOfLastMatchIndex = endOfCurrentMatchIndex;
+    }
+
+    if (!matchFound) {
+      return textToNormalize;
+    }
+
+    appendFromSourceToResult(
+        textToNormalize, normalizedResultBuilder, endOfLastMatchIndex, textToNormalize.length());
+
+    return normalizedResultBuilder.toString();
+  }
+
+  private String matchDateTimeAndConvertFromSourceToDestinationFormat(String possiblyDateTime) {
+    TemporalAccessor parsedDateTime = sourceFormat.parse(possiblyDateTime);
+    return destinationFormat.format(parsedDateTime);
+  }
+
+  private String matchDateTime(String text, Matcher matcher) {
+    return text.substring(matcher.start(DATE_TIME_GROUP_NAME), matcher.end(DATE_TIME_GROUP_NAME));
+  }
+
+  private void appendFromSourceToResult(
+      String source, StringBuilder resultBuilder, int fromIndex, int toIndex) {
+    resultBuilder.append(source, fromIndex, toIndex);
+  }
+
+  @Override
+  public String toString() {
+    return "DateTimeReplacer for pattern " + pattern.toString() + ".";
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilder.java b/src/main/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilder.java
index a56deec..458412d 100644
--- a/src/main/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilder.java
+++ b/src/main/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilder.java
@@ -1,45 +1,43 @@
 package de.cronn.assertions.validationfile.replacements;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.time.format.DateTimeFormatter;
 import java.util.Objects;
 import java.util.regex.Pattern;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 public class JsonDateTimeReplacerBuilder extends AbstractJsonReplacer {
-	private DateTimeFormatter sourceFormat;
-	private DateTimeFormatter destinationFormat;
-	private String colonGroupRegex = COLON_WITH_WHITESPACES_GROUP;
-
-	public JsonDateTimeReplacerBuilder withDestinationFormat(DateTimeFormatter destinationFormat) {
-		this.destinationFormat = destinationFormat;
-		return this;
-	}
-
-	public JsonDateTimeReplacerBuilder withSourceFormat(DateTimeFormatter sourceFormat) {
-		this.sourceFormat = sourceFormat;
-		return this;
-	}
-
-	public JsonDateTimeReplacerBuilder withColonGroupRegex(String colonGroupRegex) {
-		this.colonGroupRegex = colonGroupRegex;
-		return this;
-	}
-
-	@Override
-	public ValidationNormalizer build() {
-		Objects.requireNonNull(key);
-
-		String keyWithQuotationMarks = "\"" + key + "\"";
-		Pattern pattern = Pattern.compile(keyWithQuotationMarks + colonGroupRegex + "\"(?.+?)\"");
-
-		return new DateTimeReplacer(pattern, sourceFormat, destinationFormat);
-	}
-
-	@Override
-	protected JsonDateTimeReplacerBuilder getThis() {
-		return this;
-	}
-
-
+  private DateTimeFormatter sourceFormat;
+  private DateTimeFormatter destinationFormat;
+  private String colonGroupRegex = COLON_WITH_WHITESPACES_GROUP;
+
+  public JsonDateTimeReplacerBuilder withDestinationFormat(DateTimeFormatter destinationFormat) {
+    this.destinationFormat = destinationFormat;
+    return this;
+  }
+
+  public JsonDateTimeReplacerBuilder withSourceFormat(DateTimeFormatter sourceFormat) {
+    this.sourceFormat = sourceFormat;
+    return this;
+  }
+
+  public JsonDateTimeReplacerBuilder withColonGroupRegex(String colonGroupRegex) {
+    this.colonGroupRegex = colonGroupRegex;
+    return this;
+  }
+
+  @Override
+  public ValidationNormalizer build() {
+    Objects.requireNonNull(key);
+
+    String keyWithQuotationMarks = "\"" + key + "\"";
+    Pattern pattern =
+        Pattern.compile(keyWithQuotationMarks + colonGroupRegex + "\"(?.+?)\"");
+
+    return new DateTimeReplacer(pattern, sourceFormat, destinationFormat);
+  }
+
+  @Override
+  protected JsonDateTimeReplacerBuilder getThis() {
+    return this;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilder.java b/src/main/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilder.java
index 9fb9cef..9939f74 100644
--- a/src/main/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilder.java
+++ b/src/main/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilder.java
@@ -1,57 +1,59 @@
 package de.cronn.assertions.validationfile.replacements;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.util.Objects;
 import java.util.regex.Pattern;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 public class JsonReplacerBuilder extends AbstractJsonReplacer {
 
-	private String valueToReplace =
-			"(null|true|false|" + // booleans
-			"-?\\d+(\\.\\d+)?|" + // numbers
-			"(\".+?(? {
-	private DateTimeFormatter destinationFormat;
-	private DateTimeFormatter sourceFormat;
-	private String dateTimePatternGroup = "(?.+?)";
-
-	public XmlDateTimeReplacerBuilder withDestinationFormat(DateTimeFormatter destinationFormat) {
-		this.destinationFormat = destinationFormat;
-		return this;
-	}
-
-	public XmlDateTimeReplacerBuilder withSourceFormat(DateTimeFormatter sourceFormat) {
-		this.sourceFormat = sourceFormat;
-		return this;
-	}
-
-	private XmlDateTimeReplacerBuilder withDateTimePatternGroup(String dateTimePatternGroup) {
-		this.dateTimePatternGroup = dateTimePatternGroup;
-		return this;
-	}
-
-	@Override
-	protected ValidationNormalizer build(String startTag, String endTag) {
-		Objects.requireNonNull(sourceFormat);
-		Objects.requireNonNull(destinationFormat);
-
-		Pattern pattern = Pattern.compile(startTag + dateTimePatternGroup + endTag);
-
-		return new DateTimeReplacer(pattern, sourceFormat, destinationFormat);
-	}
-
-	@Override
-	protected XmlDateTimeReplacerBuilder getThis() {
-		return this;
-	}
-
-	public XmlDateTimeReplacerBuilder withContent(String dateToMatch) {
-		return withDateTimePatternGroup("(?" + Pattern.quote(dateToMatch) + ")");
-	}
+public class XmlDateTimeReplacerBuilder
+    extends AbstractXmlReplacerBuilder {
+  private DateTimeFormatter destinationFormat;
+  private DateTimeFormatter sourceFormat;
+  private String dateTimePatternGroup = "(?.+?)";
+
+  public XmlDateTimeReplacerBuilder withDestinationFormat(DateTimeFormatter destinationFormat) {
+    this.destinationFormat = destinationFormat;
+    return this;
+  }
+
+  public XmlDateTimeReplacerBuilder withSourceFormat(DateTimeFormatter sourceFormat) {
+    this.sourceFormat = sourceFormat;
+    return this;
+  }
+
+  private XmlDateTimeReplacerBuilder withDateTimePatternGroup(String dateTimePatternGroup) {
+    this.dateTimePatternGroup = dateTimePatternGroup;
+    return this;
+  }
+
+  @Override
+  protected ValidationNormalizer build(String startTag, String endTag) {
+    Objects.requireNonNull(sourceFormat);
+    Objects.requireNonNull(destinationFormat);
+
+    Pattern pattern = Pattern.compile(startTag + dateTimePatternGroup + endTag);
+
+    return new DateTimeReplacer(pattern, sourceFormat, destinationFormat);
+  }
+
+  @Override
+  protected XmlDateTimeReplacerBuilder getThis() {
+    return this;
+  }
+
+  public XmlDateTimeReplacerBuilder withContent(String dateToMatch) {
+    return withDateTimePatternGroup("(?" + Pattern.quote(dateToMatch) + ")");
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilder.java b/src/main/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilder.java
index 0cd8f7a..7a8ef14 100644
--- a/src/main/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilder.java
+++ b/src/main/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilder.java
@@ -1,53 +1,52 @@
 package de.cronn.assertions.validationfile.replacements;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.util.Objects;
 import java.util.regex.Pattern;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 public class XmlReplacerBuilder extends AbstractXmlReplacerBuilder {
-	public static final String DEFAULT_MASK = "[masked]";
-
-	private String contentToReplace;
-	private String replacementContent;
-
-	public XmlReplacerBuilder() {
-		withSomeContent().withReplacement(DEFAULT_MASK);
-	}
-
-	public XmlReplacerBuilder withContent(String content) {
-		this.contentToReplace = content;
-		return this;
-	}
-
-	public XmlReplacerBuilder withReplacement(String content) {
-		this.replacementContent = content;
-		return this;
-	}
-
-	public XmlReplacerBuilder withSomeContent() {
-		contentToReplace = ".+?";
-		return this;
-	}
-
-	public XmlReplacerBuilder withSomeDigitsContent() {
-		contentToReplace = "[0-9]+";
-		return this;
-	}
-
-	@Override
-	protected ValidationNormalizer build(String startTag, String endTag) {
-		Objects.requireNonNull(contentToReplace);
-		Objects.requireNonNull(replacementContent);
-
-		Pattern pattern = Pattern.compile(startTag + contentToReplace + endTag);
-		String replacement = startTag + replacementContent + endTag;
-
-		return new Replacer(pattern, replacement);
-	}
-
-	@Override
-	protected XmlReplacerBuilder getThis() {
-		return this;
-	}
+  public static final String DEFAULT_MASK = "[masked]";
+
+  private String contentToReplace;
+  private String replacementContent;
+
+  public XmlReplacerBuilder() {
+    withSomeContent().withReplacement(DEFAULT_MASK);
+  }
+
+  public XmlReplacerBuilder withContent(String content) {
+    this.contentToReplace = content;
+    return this;
+  }
+
+  public XmlReplacerBuilder withReplacement(String content) {
+    this.replacementContent = content;
+    return this;
+  }
+
+  public XmlReplacerBuilder withSomeContent() {
+    contentToReplace = ".+?";
+    return this;
+  }
+
+  public XmlReplacerBuilder withSomeDigitsContent() {
+    contentToReplace = "[0-9]+";
+    return this;
+  }
+
+  @Override
+  protected ValidationNormalizer build(String startTag, String endTag) {
+    Objects.requireNonNull(contentToReplace);
+    Objects.requireNonNull(replacementContent);
+
+    Pattern pattern = Pattern.compile(startTag + contentToReplace + endTag);
+    String replacement = startTag + replacementContent + endTag;
+
+    return new Replacer(pattern, replacement);
+  }
+
+  @Override
+  protected XmlReplacerBuilder getThis() {
+    return this;
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtils.java b/src/main/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtils.java
index d5c96d3..3340827 100644
--- a/src/main/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtils.java
+++ b/src/main/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtils.java
@@ -3,6 +3,9 @@
 import static de.cronn.assertions.validationfile.normalization.StringNormalizer.*;
 import static java.nio.file.StandardOpenOption.*;
 
+import de.cronn.assertions.validationfile.FileBasedComparisonFailure;
+import de.cronn.assertions.validationfile.TestData;
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.charset.Charset;
@@ -12,103 +15,103 @@
 import java.util.Objects;
 import java.util.regex.Pattern;
 
-import de.cronn.assertions.validationfile.FileBasedComparisonFailure;
-import de.cronn.assertions.validationfile.TestData;
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 public final class FileBasedComparisonUtils {
 
-	private static final Charset CHARSET = StandardCharsets.UTF_8;
-	private static final String NEW_FILE_HEADER_PREFIX = "=== new file \"";
-	private static final String NEW_FILE_HEADER_SUFFIX = "\" ===\n";
-	private static final Pattern ILLEGAL_WINDOWS_FILE_NAME_CHARS = Pattern.compile("[\u0000-\u001f<>:\"|?*\\\\]");
-
-	private FileBasedComparisonUtils() {
-	}
-
-	/**
-	 * do not use this directly, rather use ValidationFileAssertions#compareActualWithFile(java.lang.String, de.cronn.assertions.validationfile.normalization.ValidationNormalizer)
-	 */
-	public static void compareActualWithFileHidden(String actualOutput, String filename, ValidationNormalizer normalizer) {
-		String fixedFilename = validateAndFixFilename(filename);
-		String fileNameRawFile = fixedFilename + ".raw";
-		writeTmp(actualOutput, fileNameRawFile);
-		String normalizedOutput = normalizer != null ? normalizer.normalize(actualOutput) : actualOutput;
-		String normalizedActual = normalizeLineEndings(normalizedOutput);
-		prefillIfNecessary(fixedFilename, normalizedActual);
-		String expected = readValidationFile(fixedFilename);
-		writeOutput(normalizedActual, fixedFilename);
-		assertEquals(expected, normalizedActual, fixedFilename, fixedFilename);
-	}
-
-	public static void assertValidationFilesAreEqual(String filename1, String filename2) {
-		String file1Content = readValidationFile(filename1);
-		String file2Content = readValidationFile(filename2);
-		assertEquals(file2Content, file1Content, filename1, filename2);
-	}
-
-	public static void compareFileDiffStyle(String expected, String actual) {
-		assertEquals(expected, actual, null, null);
-	}
-
-	public static void assertEquals(String expected, String actual, String filenameExpected, String filenameActual) {
-		if (!Objects.equals(expected, actual)) {
-			throw new FileBasedComparisonFailure(expected, actual, filenameExpected, filenameActual);
-		}
-	}
-
-	public static String validateAndFixFilename(String filename) {
-		if (filename.endsWith(".txt.txt")) {
-			throw new IllegalArgumentException("Illegal filename: '" + filename + "'");
-		}
-		return ILLEGAL_WINDOWS_FILE_NAME_CHARS.matcher(filename).replaceAll("_");
-	}
-
-	public static String readValidationFile(String fileName) {
-		return read(TestData.validationFilePath(fileName));
-	}
-
-	private static String read(Path validation) {
-		try {
-			return normalizeLineEndings(new String(Files.readAllBytes(validation), CHARSET));
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	public static boolean prefillIfNecessary(String fileName, String normalizedActual) {
-		Path validationFile = TestData.validationFilePath(fileName);
-		if (!validationFile.toFile().exists() && normalizedActual != null) {
-			String header = NEW_FILE_HEADER_PREFIX + validationFile + NEW_FILE_HEADER_SUFFIX;
-			write(header + normalizedActual, validationFile);
-			return true;
-		}
-		return false;
-	}
-
-	public static void writeOutput(String actual, String fileName) {
-		write(actual, TestData.outputPath(fileName));
-	}
-
-	public static void writeTmp(String content, String fileName) {
-		writeTmp(content.getBytes(CHARSET), fileName);
-	}
-
-	public static void writeTmp(byte[] content, String fileName) {
-		write(content, TestData.tmpPath(fileName));
-	}
-
-	public static void write(String actual, Path path) {
-		write(actual.getBytes(CHARSET), path);
-	}
-
-	public static void write(byte[] actual, Path path) {
-		try {
-			Files.createDirectories(path.getParent());
-			Files.write(path, actual, TRUNCATE_EXISTING, WRITE, CREATE);
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
+  private static final Charset CHARSET = StandardCharsets.UTF_8;
+  private static final String NEW_FILE_HEADER_PREFIX = "=== new file \"";
+  private static final String NEW_FILE_HEADER_SUFFIX = "\" ===\n";
+  private static final Pattern ILLEGAL_WINDOWS_FILE_NAME_CHARS =
+      Pattern.compile("[\u0000-\u001f<>:\"|?*\\\\]");
+
+  private FileBasedComparisonUtils() {}
+
+  /**
+   * do not use this directly, rather use
+   * ValidationFileAssertions#compareActualWithFile(java.lang.String,
+   * de.cronn.assertions.validationfile.normalization.ValidationNormalizer)
+   */
+  public static void compareActualWithFileHidden(
+      String actualOutput, String filename, ValidationNormalizer normalizer) {
+    String fixedFilename = validateAndFixFilename(filename);
+    String fileNameRawFile = fixedFilename + ".raw";
+    writeTmp(actualOutput, fileNameRawFile);
+    String normalizedOutput =
+        normalizer != null ? normalizer.normalize(actualOutput) : actualOutput;
+    String normalizedActual = normalizeLineEndings(normalizedOutput);
+    prefillIfNecessary(fixedFilename, normalizedActual);
+    String expected = readValidationFile(fixedFilename);
+    writeOutput(normalizedActual, fixedFilename);
+    assertEquals(expected, normalizedActual, fixedFilename, fixedFilename);
+  }
+
+  public static void assertValidationFilesAreEqual(String filename1, String filename2) {
+    String file1Content = readValidationFile(filename1);
+    String file2Content = readValidationFile(filename2);
+    assertEquals(file2Content, file1Content, filename1, filename2);
+  }
+
+  public static void compareFileDiffStyle(String expected, String actual) {
+    assertEquals(expected, actual, null, null);
+  }
+
+  public static void assertEquals(
+      String expected, String actual, String filenameExpected, String filenameActual) {
+    if (!Objects.equals(expected, actual)) {
+      throw new FileBasedComparisonFailure(expected, actual, filenameExpected, filenameActual);
+    }
+  }
+
+  public static String validateAndFixFilename(String filename) {
+    if (filename.endsWith(".txt.txt")) {
+      throw new IllegalArgumentException("Illegal filename: '" + filename + "'");
+    }
+    return ILLEGAL_WINDOWS_FILE_NAME_CHARS.matcher(filename).replaceAll("_");
+  }
+
+  public static String readValidationFile(String fileName) {
+    return read(TestData.validationFilePath(fileName));
+  }
+
+  private static String read(Path validation) {
+    try {
+      return normalizeLineEndings(new String(Files.readAllBytes(validation), CHARSET));
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  public static boolean prefillIfNecessary(String fileName, String normalizedActual) {
+    Path validationFile = TestData.validationFilePath(fileName);
+    if (!validationFile.toFile().exists() && normalizedActual != null) {
+      String header = NEW_FILE_HEADER_PREFIX + validationFile + NEW_FILE_HEADER_SUFFIX;
+      write(header + normalizedActual, validationFile);
+      return true;
+    }
+    return false;
+  }
+
+  public static void writeOutput(String actual, String fileName) {
+    write(actual, TestData.outputPath(fileName));
+  }
+
+  public static void writeTmp(String content, String fileName) {
+    writeTmp(content.getBytes(CHARSET), fileName);
+  }
+
+  public static void writeTmp(byte[] content, String fileName) {
+    write(content, TestData.tmpPath(fileName));
+  }
+
+  public static void write(String actual, Path path) {
+    write(actual.getBytes(CHARSET), path);
+  }
+
+  public static void write(byte[] actual, Path path) {
+    try {
+      Files.createDirectories(path.getParent());
+      Files.write(path, actual, TRUNCATE_EXISTING, WRITE, CREATE);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/util/MarkdownTable.java b/src/main/java/de/cronn/assertions/validationfile/util/MarkdownTable.java
index ca7cf74..f013ec1 100644
--- a/src/main/java/de/cronn/assertions/validationfile/util/MarkdownTable.java
+++ b/src/main/java/de/cronn/assertions/validationfile/util/MarkdownTable.java
@@ -8,109 +8,115 @@
 
 public class MarkdownTable {
 
-	private final List header = new ArrayList<>();
-
-	private final List> rows = new ArrayList<>();
-
-	private List currentRow = new ArrayList<>();
-
-	public MarkdownTable() {
-	}
-
-	public MarkdownTable(List header) {
-		this.addCells(header);
-		this.nextRow();
-	}
-
-	public void addCell(Object value) {
-		currentRow.add(Objects.toString(value));
-	}
-
-	public void addCells(Collection values) {
-		values.forEach(this::addCell);
-	}
-
-	public void addCells(Object... values) {
-		for (Object value : values) {
-			addCell(value);
-		}
-	}
-
-	public void addRow(Collection values) {
-		if (!currentRow.isEmpty()) {
-			throw new IllegalStateException("Building of the previous row is not finished. Call nextRow() first.");
-		}
-		addCells(values);
-		nextRow();
-	}
-
-	public void addRow(Object... values) {
-		if (!currentRow.isEmpty()) {
-			throw new IllegalStateException("Building of the previous row is not finished. Call nextRow() first.");
-		}
-		addCells(values);
-		nextRow();
-	}
-
-	public void nextRow() {
-		if (header.isEmpty()) {
-			header.addAll(currentRow);
-		} else {
-			if (currentRow.size() != header.size()) {
-				throw new IllegalArgumentException("Current row size [" + currentRow.size() + "] should be equal to header size [" + header.size() + "].");
-			}
-			rows.add(currentRow);
-		}
-		currentRow = new ArrayList<>();
-	}
-
-	public String toString() {
-		List maxCellLengths = computeColumnsWidths();
-		if (maxCellLengths.isEmpty()) {
-			return "";
-		}
-
-		StringBuilder sb = new StringBuilder();
-		sb.append('|');
-		for (int i = 0; i < header.size(); i++) {
-			pad(header.get(i), maxCellLengths.get(i), sb);
-			sb.append('|');
-		}
-		sb.append("\n|");
-		for (int i = 0; i < header.size(); i++) {
-			repeat('-', maxCellLengths.get(i) + 2, sb);
-			sb.append('|');
-		}
-		for (List row : rows) {
-			sb.append("\n|");
-			for (int i = 0; i < row.size(); i++) {
-				pad(row.get(i), maxCellLengths.get(i), sb);
-				sb.append('|');
-			}
-		}
-		return sb.toString();
-	}
-
-	private List computeColumnsWidths() {
-		List widths = header.stream().map(String::length).collect(Collectors.toList());
-		for (int i = 0; i < header.size(); i++) {
-			for (List row : rows) {
-				widths.set(i, Math.max(row.get(i).length(), widths.get(i)));
-			}
-		}
-		return widths;
-	}
-
-	private void repeat(char ch, int times, StringBuilder sb) {
-		for (int i = 0; i < times; i++) {
-			sb.append(ch);
-		}
-	}
-
-	private void pad(String value, int size, StringBuilder sb) {
-		sb.append(' ');
-		sb.append(value);
-		repeat(' ', size - value.length(), sb);
-		sb.append(' ');
-	}
+  private final List header = new ArrayList<>();
+
+  private final List> rows = new ArrayList<>();
+
+  private List currentRow = new ArrayList<>();
+
+  public MarkdownTable() {}
+
+  public MarkdownTable(List header) {
+    this.addCells(header);
+    this.nextRow();
+  }
+
+  public void addCell(Object value) {
+    currentRow.add(Objects.toString(value));
+  }
+
+  public void addCells(Collection values) {
+    values.forEach(this::addCell);
+  }
+
+  public void addCells(Object... values) {
+    for (Object value : values) {
+      addCell(value);
+    }
+  }
+
+  public void addRow(Collection values) {
+    if (!currentRow.isEmpty()) {
+      throw new IllegalStateException(
+          "Building of the previous row is not finished. Call nextRow() first.");
+    }
+    addCells(values);
+    nextRow();
+  }
+
+  public void addRow(Object... values) {
+    if (!currentRow.isEmpty()) {
+      throw new IllegalStateException(
+          "Building of the previous row is not finished. Call nextRow() first.");
+    }
+    addCells(values);
+    nextRow();
+  }
+
+  public void nextRow() {
+    if (header.isEmpty()) {
+      header.addAll(currentRow);
+    } else {
+      if (currentRow.size() != header.size()) {
+        throw new IllegalArgumentException(
+            "Current row size ["
+                + currentRow.size()
+                + "] should be equal to header size ["
+                + header.size()
+                + "].");
+      }
+      rows.add(currentRow);
+    }
+    currentRow = new ArrayList<>();
+  }
+
+  public String toString() {
+    List maxCellLengths = computeColumnsWidths();
+    if (maxCellLengths.isEmpty()) {
+      return "";
+    }
+
+    StringBuilder sb = new StringBuilder();
+    sb.append('|');
+    for (int i = 0; i < header.size(); i++) {
+      pad(header.get(i), maxCellLengths.get(i), sb);
+      sb.append('|');
+    }
+    sb.append("\n|");
+    for (int i = 0; i < header.size(); i++) {
+      repeat('-', maxCellLengths.get(i) + 2, sb);
+      sb.append('|');
+    }
+    for (List row : rows) {
+      sb.append("\n|");
+      for (int i = 0; i < row.size(); i++) {
+        pad(row.get(i), maxCellLengths.get(i), sb);
+        sb.append('|');
+      }
+    }
+    return sb.toString();
+  }
+
+  private List computeColumnsWidths() {
+    List widths = header.stream().map(String::length).collect(Collectors.toList());
+    for (int i = 0; i < header.size(); i++) {
+      for (List row : rows) {
+        widths.set(i, Math.max(row.get(i).length(), widths.get(i)));
+      }
+    }
+    return widths;
+  }
+
+  private void repeat(char ch, int times, StringBuilder sb) {
+    for (int i = 0; i < times; i++) {
+      sb.append(ch);
+    }
+  }
+
+  private void pad(String value, int size, StringBuilder sb) {
+    sb.append(' ');
+    sb.append(value);
+    repeat(' ', size - value.length(), sb);
+    sb.append(' ');
+  }
 }
diff --git a/src/main/java/de/cronn/assertions/validationfile/util/TestNameUtils.java b/src/main/java/de/cronn/assertions/validationfile/util/TestNameUtils.java
index 7a893d7..b2f2d1b 100644
--- a/src/main/java/de/cronn/assertions/validationfile/util/TestNameUtils.java
+++ b/src/main/java/de/cronn/assertions/validationfile/util/TestNameUtils.java
@@ -2,24 +2,23 @@
 
 public final class TestNameUtils {
 
-	private TestNameUtils() {
-	}
+  private TestNameUtils() {}
 
-	public static String getTestName(Class aClass, String methodName) {
-		return join(enclosingClassesUpstream(aClass), methodName);
-	}
+  public static String getTestName(Class aClass, String methodName) {
+    return join(enclosingClassesUpstream(aClass), methodName);
+  }
 
-	private static String enclosingClassesUpstream(Class aClass) {
-		String classHierarchy = aClass.getSimpleName();
-		Class enclosingClass = aClass.getEnclosingClass();
-		while (enclosingClass != null) {
-			classHierarchy = join(enclosingClass.getSimpleName(), classHierarchy);
-			enclosingClass = enclosingClass.getEnclosingClass();
-		}
-		return classHierarchy;
-	}
+  private static String enclosingClassesUpstream(Class aClass) {
+    String classHierarchy = aClass.getSimpleName();
+    Class enclosingClass = aClass.getEnclosingClass();
+    while (enclosingClass != null) {
+      classHierarchy = join(enclosingClass.getSimpleName(), classHierarchy);
+      enclosingClass = enclosingClass.getEnclosingClass();
+    }
+    return classHierarchy;
+  }
 
-	private static String join(String element, String other) {
-		return other.startsWith("_") ? (element + other) : (element + "_" + other);
-	}
+  private static String join(String element, String other) {
+    return other.startsWith("_") ? (element + other) : (element + "_" + other);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/FileBasedComparisonFailureTest.java b/src/test/java/de/cronn/assertions/validationfile/FileBasedComparisonFailureTest.java
index 78be36d..b5eb2b5 100644
--- a/src/test/java/de/cronn/assertions/validationfile/FileBasedComparisonFailureTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/FileBasedComparisonFailureTest.java
@@ -6,97 +6,106 @@
 
 class FileBasedComparisonFailureTest {
 
-	@Test
-	void testMessage_givenAllParameters() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure("expected", "actual", "filenameExpected", "filenameActual");
-
-		assertThat(failure.isExpectedDefined()).isTrue();
-		assertThat(failure.getExpected().getValue()).isEqualTo("expected");
-		assertThat(failure.isActualDefined()).isTrue();
-		assertThat(failure.getActual().getValue()).isEqualTo("actual");
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected/filenameExpected\n" +
-			"+++ actual/filenameActual\n" +
-			"@@ -1,1 +1,1 @@\n" +
-			"-expected\n" +
-			"+actual");
-	}
-
-	@Test
-	void testMessage_expectedAndActualEquals_thenExceptionThrownOnConstruction() {
-		assertThatCode(() -> new FileBasedComparisonFailure("expected", "expected", null, null))
-			.hasMessage("expected and actual values are equal");
-	}
-
-	@Test
-	void testMessage_nullGivenAsExpected() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure(null, "actual", null, null);
-
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected\n" +
-			"+++ actual\n" +
-			"@@ -1,0 +1,1 @@\n" +
-			"+actual");
-	}
-
-	@Test
-	void testMessage_nullGivenAsActual() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure("expected", null, null, null);
-
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected\n" +
-			"+++ actual\n" +
-			"@@ -1,1 +1,0 @@\n" +
-			"-expected");
-	}
-
-	@Test
-	void testMessage_nullGivenAsFileNameActual() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure("expected", "actual", "filenameExpected", null);
-
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected/filenameExpected\n" +
-			"+++ actual\n" +
-			"@@ -1,1 +1,1 @@\n" +
-			"-expected\n" +
-			"+actual");
-	}
-
-	@Test
-	void testMessage_nullGivenAsFileNameExpected() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure("expected", "actual", null, "filenameActual");
-
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected\n" +
-			"+++ actual/filenameActual\n" +
-			"@@ -1,1 +1,1 @@\n" +
-			"-expected\n" +
-			"+actual");
-	}
-
-	@Test
-	void testMessage_nullGivenAsBothFileNames() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure("expected", "actual", null, null);
-
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected\n" +
-			"+++ actual\n" +
-			"@@ -1,1 +1,1 @@\n" +
-			"-expected\n" +
-			"+actual");
-	}
-
-	@Test
-	void testMessage_shouldGenerateMultilineDiff() {
-		FileBasedComparisonFailure failure = new FileBasedComparisonFailure("line1\nline2", "line1\nline2\nline3", null, null);
-
-		assertThat(failure.getMessage()).isEqualTo("\n" +
-			"--- expected\n" +
-			"+++ actual\n" +
-			"@@ -1,2 +1,3 @@\n" +
-			" line1\n" +
-			" line2\n" +
-			"+line3");
-	}
-
+  @Test
+  void testMessage_givenAllParameters() {
+    FileBasedComparisonFailure failure =
+        new FileBasedComparisonFailure("expected", "actual", "filenameExpected", "filenameActual");
+
+    assertThat(failure.isExpectedDefined()).isTrue();
+    assertThat(failure.getExpected().getValue()).isEqualTo("expected");
+    assertThat(failure.isActualDefined()).isTrue();
+    assertThat(failure.getActual().getValue()).isEqualTo("actual");
+    assertThat(failure.getMessage())
+        .isEqualTo(
+            "\n"
+                + "--- expected/filenameExpected\n"
+                + "+++ actual/filenameActual\n"
+                + "@@ -1,1 +1,1 @@\n"
+                + "-expected\n"
+                + "+actual");
+  }
+
+  @Test
+  void testMessage_expectedAndActualEquals_thenExceptionThrownOnConstruction() {
+    assertThatCode(() -> new FileBasedComparisonFailure("expected", "expected", null, null))
+        .hasMessage("expected and actual values are equal");
+  }
+
+  @Test
+  void testMessage_nullGivenAsExpected() {
+    FileBasedComparisonFailure failure = new FileBasedComparisonFailure(null, "actual", null, null);
+
+    assertThat(failure.getMessage())
+        .isEqualTo("\n" + "--- expected\n" + "+++ actual\n" + "@@ -1,0 +1,1 @@\n" + "+actual");
+  }
+
+  @Test
+  void testMessage_nullGivenAsActual() {
+    FileBasedComparisonFailure failure =
+        new FileBasedComparisonFailure("expected", null, null, null);
+
+    assertThat(failure.getMessage())
+        .isEqualTo("\n" + "--- expected\n" + "+++ actual\n" + "@@ -1,1 +1,0 @@\n" + "-expected");
+  }
+
+  @Test
+  void testMessage_nullGivenAsFileNameActual() {
+    FileBasedComparisonFailure failure =
+        new FileBasedComparisonFailure("expected", "actual", "filenameExpected", null);
+
+    assertThat(failure.getMessage())
+        .isEqualTo(
+            "\n"
+                + "--- expected/filenameExpected\n"
+                + "+++ actual\n"
+                + "@@ -1,1 +1,1 @@\n"
+                + "-expected\n"
+                + "+actual");
+  }
+
+  @Test
+  void testMessage_nullGivenAsFileNameExpected() {
+    FileBasedComparisonFailure failure =
+        new FileBasedComparisonFailure("expected", "actual", null, "filenameActual");
+
+    assertThat(failure.getMessage())
+        .isEqualTo(
+            "\n"
+                + "--- expected\n"
+                + "+++ actual/filenameActual\n"
+                + "@@ -1,1 +1,1 @@\n"
+                + "-expected\n"
+                + "+actual");
+  }
+
+  @Test
+  void testMessage_nullGivenAsBothFileNames() {
+    FileBasedComparisonFailure failure =
+        new FileBasedComparisonFailure("expected", "actual", null, null);
+
+    assertThat(failure.getMessage())
+        .isEqualTo(
+            "\n"
+                + "--- expected\n"
+                + "+++ actual\n"
+                + "@@ -1,1 +1,1 @@\n"
+                + "-expected\n"
+                + "+actual");
+  }
+
+  @Test
+  void testMessage_shouldGenerateMultilineDiff() {
+    FileBasedComparisonFailure failure =
+        new FileBasedComparisonFailure("line1\nline2", "line1\nline2\nline3", null, null);
+
+    assertThat(failure.getMessage())
+        .isEqualTo(
+            "\n"
+                + "--- expected\n"
+                + "+++ actual\n"
+                + "@@ -1,2 +1,3 @@\n"
+                + " line1\n"
+                + " line2\n"
+                + "+line3");
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_OverrideValidationFileName.java b/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_OverrideValidationFileName.java
index 712e847..89f85e4 100644
--- a/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_OverrideValidationFileName.java
+++ b/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_OverrideValidationFileName.java
@@ -2,48 +2,48 @@
 
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.extension.CleanupValidationFilesAfterAllTests;
+import de.cronn.assertions.validationfile.util.TestNameUtils;
 import java.nio.file.Path;
-
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 
-import de.cronn.assertions.validationfile.extension.CleanupValidationFilesAfterAllTests;
-import de.cronn.assertions.validationfile.util.TestNameUtils;
-
 @ExtendWith(CleanupValidationFilesAfterAllTests.class)
 class ValidationFileAssertionsTest_OverrideValidationFileName implements ValidationFileAssertions {
 
-	private TestInfo testInfo;
+  private TestInfo testInfo;
 
-	@BeforeEach
-	void init(TestInfo testInfo) {
-		this.testInfo = testInfo;
-	}
+  @BeforeEach
+  void init(TestInfo testInfo) {
+    this.testInfo = testInfo;
+  }
 
-	@Override
-	public String getTestName() {
-		return TestNameUtils
-			.getTestName(getClass(), testInfo.getTestMethod().orElseThrow(IllegalArgumentException::new).getName());
-	}
+  @Override
+  public String getTestName() {
+    return TestNameUtils.getTestName(
+        getClass(), testInfo.getTestMethod().orElseThrow(IllegalArgumentException::new).getName());
+  }
 
-	@Override
-	public String getValidationFileName(String baseName, FileExtension extension) {
-		String validationFileName = ValidationFileAssertions.super.getValidationFileName(baseName, extension);
-		String directory = getClass().getPackage().getName().replaceAll("\\.", "/") + "/";
-		return directory + validationFileName;
-	}
+  @Override
+  public String getValidationFileName(String baseName, FileExtension extension) {
+    String validationFileName =
+        ValidationFileAssertions.super.getValidationFileName(baseName, extension);
+    String directory = getClass().getPackage().getName().replaceAll("\\.", "/") + "/";
+    return directory + validationFileName;
+  }
 
-	@Test
-	void assertValidationFileName() {
-		Path file = TestData.TEST_VALIDATION_DATA_DIR.resolve(
-			"de/cronn/assertions/validationfile/ValidationFileAssertionsTest_OverrideValidationFileName_assertValidationFileName.txt");
+  @Test
+  void assertValidationFileName() {
+    Path file =
+        TestData.TEST_VALIDATION_DATA_DIR.resolve(
+            "de/cronn/assertions/validationfile/ValidationFileAssertionsTest_OverrideValidationFileName_assertValidationFileName.txt");
 
-		assertThat(file).doesNotExist();
+    assertThat(file).doesNotExist();
 
-		catchThrowableOfType(() -> assertWithFile("lorem ipsum"), AssertionError.class);
+    catchThrowableOfType(() -> assertWithFile("lorem ipsum"), AssertionError.class);
 
-		assertThat(file).exists();
-	}
+    assertThat(file).exists();
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_SoftAssertions.java b/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_SoftAssertions.java
index 7d799e0..a003e32 100644
--- a/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_SoftAssertions.java
+++ b/src/test/java/de/cronn/assertions/validationfile/ValidationFileAssertionsTest_SoftAssertions.java
@@ -2,68 +2,67 @@
 
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.extension.CleanupValidationFilesAfterAllTests;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
-import de.cronn.assertions.validationfile.extension.CleanupValidationFilesAfterAllTests;
-
 @ExtendWith(CleanupValidationFilesAfterAllTests.class)
 class ValidationFileAssertionsTest_SoftAssertions {
 
-	SoftAssertions softly = new SoftAssertions();
-
-	@Nested
-	class FailedAssertionHandlerProvided implements ValidationFileAssertions {
+  SoftAssertions softly = new SoftAssertions();
 
-		@Override
-		public String getTestName() {
-			return "any-test-name";
-		}
+  @Nested
+  class FailedAssertionHandlerProvided implements ValidationFileAssertions {
 
-		@Override
-		public FailedAssertionHandler failedAssertionHandler() {
-			return callable -> softly.check(callable::call);
-		}
+    @Override
+    public String getTestName() {
+      return "any-test-name";
+    }
 
-		@Test
-		void testCompareTwoActualsWithFilesAndSoftlyFailAfter() {
-			assertThatThrownBy(() -> {
-				assertWithFile("lorem ipsum");
-				assertWithFile("another");
-				softly.fail("and one normal failure after");
-				assertWithFile("not reached without soft assertions");
-				softly.assertAll();
-			}).hasMessageContainingAll(
-				"lorem ipsum",
-				"another",
-				"and one normal failure after",
-				"not reached without soft assertions"
-			);
-		}
-	}
+    @Override
+    public FailedAssertionHandler failedAssertionHandler() {
+      return callable -> softly.check(callable::call);
+    }
 
-	@Nested
-	class NoFailedAssertionHandler implements ValidationFileAssertions {
+    @Test
+    void testCompareTwoActualsWithFilesAndSoftlyFailAfter() {
+      assertThatThrownBy(
+              () -> {
+                assertWithFile("lorem ipsum");
+                assertWithFile("another");
+                softly.fail("and one normal failure after");
+                assertWithFile("not reached without soft assertions");
+                softly.assertAll();
+              })
+          .hasMessageContainingAll(
+              "lorem ipsum",
+              "another",
+              "and one normal failure after",
+              "not reached without soft assertions");
+    }
+  }
 
-		@Override
-		public String getTestName() {
-			return "any-test-name";
-		}
+  @Nested
+  class NoFailedAssertionHandler implements ValidationFileAssertions {
 
-		@Test
-		void testCompareTwoActualsWithFilesAndSoftlyFailAfter() {
-			assertThatThrownBy(() -> {
-				assertWithFile("lorem ipsum");
-				assertWithFile("another");
-				softly.fail("and one normal failure after");
-				assertWithFile("not reached without soft assertions");
-				softly.assertAll();
-			}).hasMessageContainingAll(
-				"lorem ipsum"
-			);
-		}
-	}
+    @Override
+    public String getTestName() {
+      return "any-test-name";
+    }
 
+    @Test
+    void testCompareTwoActualsWithFilesAndSoftlyFailAfter() {
+      assertThatThrownBy(
+              () -> {
+                assertWithFile("lorem ipsum");
+                assertWithFile("another");
+                softly.fail("and one normal failure after");
+                assertWithFile("not reached without soft assertions");
+                softly.assertAll();
+              })
+          .hasMessageContainingAll("lorem ipsum");
+    }
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/extension/CleanupValidationFilesAfterAllTests.java b/src/test/java/de/cronn/assertions/validationfile/extension/CleanupValidationFilesAfterAllTests.java
index fc8c1dc..68b00c0 100644
--- a/src/test/java/de/cronn/assertions/validationfile/extension/CleanupValidationFilesAfterAllTests.java
+++ b/src/test/java/de/cronn/assertions/validationfile/extension/CleanupValidationFilesAfterAllTests.java
@@ -2,6 +2,7 @@
 
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.TestData;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -10,61 +11,58 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-
 import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
 
-import de.cronn.assertions.validationfile.TestData;
-
 public class CleanupValidationFilesAfterAllTests implements BeforeAllCallback, AfterAllCallback {
 
-	private Set filesBefore;
+  private Set filesBefore;
 
-	@Override
-	public void beforeAll(ExtensionContext context) throws IOException {
-		filesBefore = collectAllFiles();
-	}
+  @Override
+  public void beforeAll(ExtensionContext context) throws IOException {
+    filesBefore = collectAllFiles();
+  }
 
-	@Override
-	public void afterAll(ExtensionContext context) throws IOException {
-		cleanupFiles();
-	}
+  @Override
+  public void afterAll(ExtensionContext context) throws IOException {
+    cleanupFiles();
+  }
 
-	public void cleanupFiles() throws IOException {
-		Set filesToRemove = collectAddedFiles();
-		for (Path file : filesToRemove) {
-			Files.deleteIfExists(file);
-		}
-	}
+  public void cleanupFiles() throws IOException {
+    Set filesToRemove = collectAddedFiles();
+    for (Path file : filesToRemove) {
+      Files.deleteIfExists(file);
+    }
+  }
 
-	Set getFilesBeforeClass() {
-		return filesBefore;
-	}
+  Set getFilesBeforeClass() {
+    return filesBefore;
+  }
 
-	public Set collectAddedFiles() throws IOException {
-		Set allFiles = collectAllFiles();
-		allFiles.removeAll(getFilesBeforeClass());
-		return allFiles;
-	}
+  public Set collectAddedFiles() throws IOException {
+    Set allFiles = collectAllFiles();
+    allFiles.removeAll(getFilesBeforeClass());
+    return allFiles;
+  }
 
-	Set collectAllFiles() throws IOException {
-		Set files = new LinkedHashSet<>();
-		files.addAll(listFiles(TestData.TEST_VALIDATION_DATA_DIR));
-		files.addAll(listFiles(TestData.TEST_OUTPUT_DATA_DIR));
-		files.addAll(listFiles(TestData.TEST_TEMPORARY_DATA_DIR));
-		return files;
-	}
+  Set collectAllFiles() throws IOException {
+    Set files = new LinkedHashSet<>();
+    files.addAll(listFiles(TestData.TEST_VALIDATION_DATA_DIR));
+    files.addAll(listFiles(TestData.TEST_OUTPUT_DATA_DIR));
+    files.addAll(listFiles(TestData.TEST_TEMPORARY_DATA_DIR));
+    return files;
+  }
 
-	private Set listFiles(Path dir) throws IOException {
-		if (!Files.exists(dir)) {
-			return Collections.emptySet();
-		}
-		assertThat(Files.isDirectory(dir)).as("Expected directory, but was: %s", dir).isTrue();
-		try (Stream paths = Files.walk(dir)) {
-			return paths.filter(Files::isRegularFile)
-				.collect(Collectors.toCollection(LinkedHashSet::new));
-		}
-	}
+  private Set listFiles(Path dir) throws IOException {
+    if (!Files.exists(dir)) {
+      return Collections.emptySet();
+    }
+    assertThat(Files.isDirectory(dir)).as("Expected directory, but was: %s", dir).isTrue();
+    try (Stream paths = Files.walk(dir)) {
+      return paths
+          .filter(Files::isRegularFile)
+          .collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+  }
 }
-
diff --git a/src/test/java/de/cronn/assertions/validationfile/extension/ValidationFilesTestHelper.java b/src/test/java/de/cronn/assertions/validationfile/extension/ValidationFilesTestHelper.java
index 3dfd2e3..0dce089 100644
--- a/src/test/java/de/cronn/assertions/validationfile/extension/ValidationFilesTestHelper.java
+++ b/src/test/java/de/cronn/assertions/validationfile/extension/ValidationFilesTestHelper.java
@@ -1,5 +1,6 @@
 package de.cronn.assertions.validationfile.extension;
 
+import de.cronn.assertions.validationfile.TestData;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -9,42 +10,41 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import de.cronn.assertions.validationfile.TestData;
-
 public class ValidationFilesTestHelper extends CleanupValidationFilesAfterAllTests {
 
-	private static final Charset CHARSET = StandardCharsets.UTF_8;
-
-	public void copyOutputToValidation(String validationFileName) throws IOException {
-		Path source = TestData.outputPath(validationFileName);
-		Path target = TestData.validationFilePath(validationFileName);
-		Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
-	}
-
-	public List linesDiffOutputValidation(String validationFileName) throws IOException {
-		return linesDiff(TestData.outputPath(validationFileName), TestData.validationFilePath(validationFileName));
-	}
-
-	public List linesDiff(Path path1, Path path2) throws IOException {
-		List content1 = readLines(path1);
-		List content2 = readLines(path2);
-
-		List diff = new ArrayList<>();
-		for (String line : content1) {
-			if (!content2.contains(line)) {
-				diff.add("+" + line);
-			}
-		}
-		for (String line : content2) {
-			if (!content1.contains(line)) {
-				diff.add("-" + line);
-			}
-		}
-
-		return diff;
-	}
-
-	private List readLines(Path path) throws IOException {
-		return Files.readAllLines(path, CHARSET);
-	}
+  private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+  public void copyOutputToValidation(String validationFileName) throws IOException {
+    Path source = TestData.outputPath(validationFileName);
+    Path target = TestData.validationFilePath(validationFileName);
+    Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+  }
+
+  public List linesDiffOutputValidation(String validationFileName) throws IOException {
+    return linesDiff(
+        TestData.outputPath(validationFileName), TestData.validationFilePath(validationFileName));
+  }
+
+  public List linesDiff(Path path1, Path path2) throws IOException {
+    List content1 = readLines(path1);
+    List content2 = readLines(path2);
+
+    List diff = new ArrayList<>();
+    for (String line : content1) {
+      if (!content2.contains(line)) {
+        diff.add("+" + line);
+      }
+    }
+    for (String line : content2) {
+      if (!content1.contains(line)) {
+        diff.add("-" + line);
+      }
+    }
+
+    return diff;
+  }
+
+  private List readLines(Path path) throws IOException {
+    return Files.readAllLines(path, CHARSET);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest.java b/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest.java
index 6486a9d..e40f708 100644
--- a/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest.java
@@ -6,7 +6,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -17,84 +16,93 @@
 
 class JUnit5ValidationFileAssertionsTest implements JUnit5ValidationFileAssertions {
 
-	static List testNamesBefore = new ArrayList<>();
-	static List testNamesAfter = new ArrayList<>();
-
-	@BeforeEach
-	void setUp() {
-		testNamesBefore.add(getTestName());
-	}
-
-	@AfterEach
-	void tearDown() {
-		testNamesAfter.add(getTestName());
-	}
-
-	@AfterAll
-	static void afterAll() {
-		assertThat(testNamesBefore).containsExactlyInAnyOrder(
-			"JUnit5ValidationFileAssertionsTest_otherTest",
-			"JUnit5ValidationFileAssertionsTest_myTest",
-			"JUnit5ValidationFileAssertionsTest_dynamicTests",
-			"JUnit5ValidationFileAssertionsTest_NestedTestWithExplicitTraits_nestedTestExplicit",
-			"JUnit5ValidationFileAssertionsTest_NestedTest_nestedTest",
-			"JUnit5ValidationFileAssertionsTest_NestedTest_NestedNestedTest_nestedNestedTest"
-		);
-		assertThat(testNamesAfter).containsExactlyInAnyOrder(
-			"JUnit5ValidationFileAssertionsTest_otherTest",
-			"JUnit5ValidationFileAssertionsTest_myTest",
-			"JUnit5ValidationFileAssertionsTest_dynamicTests",
-			"JUnit5ValidationFileAssertionsTest_NestedTestWithExplicitTraits_nestedTestExplicit",
-			"JUnit5ValidationFileAssertionsTest_NestedTest_nestedTest",
-			"JUnit5ValidationFileAssertionsTest_NestedTest_NestedNestedTest_nestedNestedTest"
-		);
-	}
-
-	@Test
-	void myTest() {
-		assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_myTest");
-	}
-
-	@Test
-	void otherTest() {
-		assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_otherTest");
-	}
-
-	@Nested
-	class NestedTest {
-
-		@Nested
-		class NestedNestedTest {
-
-			@Test
-			void nestedNestedTest() {
-				assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_NestedTest_NestedNestedTest_nestedNestedTest");
-			}
-		}
-
-		@Test
-		void nestedTest() {
-			assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_NestedTest_nestedTest");
-		}
-	}
-
-	@Nested
-	class NestedTestWithExplicitTraits implements JUnit5ValidationFileAssertions {
-
-		@Test
-		void nestedTestExplicit() {
-			assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_NestedTestWithExplicitTraits_nestedTestExplicit");
-		}
-	}
-
-	@TestFactory
-	Collection dynamicTests() {
-		return Arrays.asList(
-			DynamicTest.dynamicTest("dynamic test", () ->
-				assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_dynamicTests")),
-			DynamicTest.dynamicTest("other dynamic test", () ->
-				assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_dynamicTests"))
-		);
-	}
-
+  static List testNamesBefore = new ArrayList<>();
+  static List testNamesAfter = new ArrayList<>();
+
+  @BeforeEach
+  void setUp() {
+    testNamesBefore.add(getTestName());
+  }
+
+  @AfterEach
+  void tearDown() {
+    testNamesAfter.add(getTestName());
+  }
+
+  @AfterAll
+  static void afterAll() {
+    assertThat(testNamesBefore)
+        .containsExactlyInAnyOrder(
+            "JUnit5ValidationFileAssertionsTest_otherTest",
+            "JUnit5ValidationFileAssertionsTest_myTest",
+            "JUnit5ValidationFileAssertionsTest_dynamicTests",
+            "JUnit5ValidationFileAssertionsTest_NestedTestWithExplicitTraits_nestedTestExplicit",
+            "JUnit5ValidationFileAssertionsTest_NestedTest_nestedTest",
+            "JUnit5ValidationFileAssertionsTest_NestedTest_NestedNestedTest_nestedNestedTest");
+    assertThat(testNamesAfter)
+        .containsExactlyInAnyOrder(
+            "JUnit5ValidationFileAssertionsTest_otherTest",
+            "JUnit5ValidationFileAssertionsTest_myTest",
+            "JUnit5ValidationFileAssertionsTest_dynamicTests",
+            "JUnit5ValidationFileAssertionsTest_NestedTestWithExplicitTraits_nestedTestExplicit",
+            "JUnit5ValidationFileAssertionsTest_NestedTest_nestedTest",
+            "JUnit5ValidationFileAssertionsTest_NestedTest_NestedNestedTest_nestedNestedTest");
+  }
+
+  @Test
+  void myTest() {
+    assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_myTest");
+  }
+
+  @Test
+  void otherTest() {
+    assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_otherTest");
+  }
+
+  @Nested
+  class NestedTest {
+
+    @Nested
+    class NestedNestedTest {
+
+      @Test
+      void nestedNestedTest() {
+        assertThat(getTestName())
+            .isEqualTo(
+                "JUnit5ValidationFileAssertionsTest_NestedTest_NestedNestedTest_nestedNestedTest");
+      }
+    }
+
+    @Test
+    void nestedTest() {
+      assertThat(getTestName())
+          .isEqualTo("JUnit5ValidationFileAssertionsTest_NestedTest_nestedTest");
+    }
+  }
+
+  @Nested
+  class NestedTestWithExplicitTraits implements JUnit5ValidationFileAssertions {
+
+    @Test
+    void nestedTestExplicit() {
+      assertThat(getTestName())
+          .isEqualTo(
+              "JUnit5ValidationFileAssertionsTest_NestedTestWithExplicitTraits_nestedTestExplicit");
+    }
+  }
+
+  @TestFactory
+  Collection dynamicTests() {
+    return Arrays.asList(
+        DynamicTest.dynamicTest(
+            "dynamic test",
+            () ->
+                assertThat(getTestName())
+                    .isEqualTo("JUnit5ValidationFileAssertionsTest_dynamicTests")),
+        DynamicTest.dynamicTest(
+            "other dynamic test",
+            () ->
+                assertThat(getTestName())
+                    .isEqualTo("JUnit5ValidationFileAssertionsTest_dynamicTests")));
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest_Concurrent.java b/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest_Concurrent.java
index cc9b119..25b064a 100644
--- a/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest_Concurrent.java
+++ b/src/test/java/de/cronn/assertions/validationfile/junit5/JUnit5ValidationFileAssertionsTest_Concurrent.java
@@ -9,18 +9,18 @@
 @Execution(ExecutionMode.CONCURRENT)
 class JUnit5ValidationFileAssertionsTest_Concurrent implements JUnit5ValidationFileAssertions {
 
-	@Test
-	void testOne() {
-		assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_Concurrent_testOne");
-	}
+  @Test
+  void testOne() {
+    assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_Concurrent_testOne");
+  }
 
-	@Test
-	void testTwo() {
-		assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_Concurrent_testTwo");
-	}
+  @Test
+  void testTwo() {
+    assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_Concurrent_testTwo");
+  }
 
-	@Test
-	void testThree() {
-		assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_Concurrent_testThree");
-	}
+  @Test
+  void testThree() {
+    assertThat(getTestName()).isEqualTo("JUnit5ValidationFileAssertionsTest_Concurrent_testThree");
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/normalization/IdNormalizerTest.java b/src/test/java/de/cronn/assertions/validationfile/normalization/IdNormalizerTest.java
index 560f85e..45cd783 100644
--- a/src/test/java/de/cronn/assertions/validationfile/normalization/IdNormalizerTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/normalization/IdNormalizerTest.java
@@ -4,112 +4,123 @@
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
 import org.junit.jupiter.api.Test;
 
 class IdNormalizerTest {
 
-	@Test
-	void testExtract() {
-		String input = "id=8 id=6 id=8";
-
-		Pattern p = Pattern.compile("id=(\\p{Alnum}*)");
-
-		Matcher m = p.matcher(input);
-		assertThat(m.find()).isTrue();
-		assertThat(m.group(1)).isEqualTo("8");
-		assertThat(m.find()).isTrue();
-		assertThat(m.group(1)).isEqualTo("6");
-		assertThat(m.find()).isTrue();
-		assertThat(m.group(1)).isEqualTo("8");
-		assertThat(m.find()).isFalse();
-	}
-
-	@Test
-	void testNormalize() {
-		String input = "id=8 id=6 id=8 xid=1 id=2";
-		String expected = "id=1 id=2 id=1 xid=3 id=4";
-		String regex = "id=(\\p{Alnum}*)";
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalizeWithoutIdMapping() {
-		String input = "id=8 id=6 id=8 xid=1 id=2";
-		String expected = "id=1 id=2 id=3 xid=4 id=5";
-		String regex = "id=(\\p{Alnum}*)";
-
-		assertThat(new IdNormalizer(new IncrementingIdProvider(), "", false, regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalize2() {
-		String input = "id=8 xid=6 id=8ssss id=1 id=2";
-		String expected = "id=1 xid=6 id=2 id=3 id=4";
-		String regex = "\\bid=(\\p{Alnum}*)";
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalizeWithTwoRegexps() {
-		String input = "id=8 xid=6 id=8 id=6 id=2 xid=8";
-		String expected = "id=1 xid=2 id=1 id=2 id=3 xid=1";
-		String[] regex = { "\\bid=(\\p{Alnum}*)", "\\bxid=(\\p{Alnum}*)" };
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalizeCanGrowSize() {
-		String input = "id=1 id=2 id=3 id=4 id=5 id=6 id=7 id=8 id=9 id=0 id=A id=B";
-		String expected = "id=1 id=2 id=3 id=4 id=5 id=6 id=7 id=8 id=9 id=10 id=11 id=12";
-		String regex = "\\bid=(\\p{Alnum}*)";
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalize3() {
-		String input = "id=id\r\n"
-			+ "id=5 id= id= id= id=\"id=2\" bar==id baz";
-		String expected = "id=1\r\n"
-			+ "id=2 id=3 id=3 id=4 id=5 bar==id baz";
-		String regex = "\\bid=(\\p{Graph}+)";
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalize4() {
-		String input = "foo id=id\r\nid=id=\"id=self\" bar";
-		String expected = "foo id=1\r\nid=2 bar";
-		String regex = "\\bid=(\\p{Graph}+)";
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalizeWithRegexpContainingTextAfterGroup() {
-		String input = "id=\"8\" id=\"6\" id=\"8\"";
-		String expected = "id=\"1\" id=\"2\" id=\"1\"";
-		String regex = "\\bid=\"(\\p{Alnum}*)\"";
-
-		assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalizeEscapedString() throws Exception {
-		String input = "change: \"jw,lq9F37x/;b\\\\\" --> \"Q;!zio$E\\\"ea,y\\\\a{\", field : \"Q;!zio$E\\\"ea,y\\\\a{\"";
-
-		// Q;!zioEea,y\\a{
-
-		String result = new IdNormalizer(new IncrementingIdProvider(), "id-", //
-			"change: \"(" + IdNormalizer.ESCAPED_STRING + ")\" --> \"" + IdNormalizer.ESCAPED_STRING + "\"", //
-			"change: \"" + IdNormalizer.ESCAPED_STRING + "\" --> \"(" + IdNormalizer.ESCAPED_STRING + ")\"", //
-			"field : \"(" + IdNormalizer.ESCAPED_STRING + ")\"").normalize(input);
-
-		assertThat(result).isEqualTo("change: \"id-1\" --> \"id-2\", field : \"id-2\"");
-	}
+  @Test
+  void testExtract() {
+    String input = "id=8 id=6 id=8";
+
+    Pattern p = Pattern.compile("id=(\\p{Alnum}*)");
+
+    Matcher m = p.matcher(input);
+    assertThat(m.find()).isTrue();
+    assertThat(m.group(1)).isEqualTo("8");
+    assertThat(m.find()).isTrue();
+    assertThat(m.group(1)).isEqualTo("6");
+    assertThat(m.find()).isTrue();
+    assertThat(m.group(1)).isEqualTo("8");
+    assertThat(m.find()).isFalse();
+  }
+
+  @Test
+  void testNormalize() {
+    String input = "id=8 id=6 id=8 xid=1 id=2";
+    String expected = "id=1 id=2 id=1 xid=3 id=4";
+    String regex = "id=(\\p{Alnum}*)";
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalizeWithoutIdMapping() {
+    String input = "id=8 id=6 id=8 xid=1 id=2";
+    String expected = "id=1 id=2 id=3 xid=4 id=5";
+    String regex = "id=(\\p{Alnum}*)";
+
+    assertThat(new IdNormalizer(new IncrementingIdProvider(), "", false, regex).normalize(input))
+        .isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalize2() {
+    String input = "id=8 xid=6 id=8ssss id=1 id=2";
+    String expected = "id=1 xid=6 id=2 id=3 id=4";
+    String regex = "\\bid=(\\p{Alnum}*)";
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalizeWithTwoRegexps() {
+    String input = "id=8 xid=6 id=8 id=6 id=2 xid=8";
+    String expected = "id=1 xid=2 id=1 id=2 id=3 xid=1";
+    String[] regex = {"\\bid=(\\p{Alnum}*)", "\\bxid=(\\p{Alnum}*)"};
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalizeCanGrowSize() {
+    String input = "id=1 id=2 id=3 id=4 id=5 id=6 id=7 id=8 id=9 id=0 id=A id=B";
+    String expected = "id=1 id=2 id=3 id=4 id=5 id=6 id=7 id=8 id=9 id=10 id=11 id=12";
+    String regex = "\\bid=(\\p{Alnum}*)";
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalize3() {
+    String input = "id=id\r\n" + "id=5 id= id= id= id=\"id=2\" bar==id baz";
+    String expected = "id=1\r\n" + "id=2 id=3 id=3 id=4 id=5 bar==id baz";
+    String regex = "\\bid=(\\p{Graph}+)";
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalize4() {
+    String input = "foo id=id\r\nid=id=\"id=self\" bar";
+    String expected = "foo id=1\r\nid=2 bar";
+    String regex = "\\bid=(\\p{Graph}+)";
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalizeWithRegexpContainingTextAfterGroup() {
+    String input = "id=\"8\" id=\"6\" id=\"8\"";
+    String expected = "id=\"1\" id=\"2\" id=\"1\"";
+    String regex = "\\bid=\"(\\p{Alnum}*)\"";
+
+    assertThat(new IdNormalizer(regex).normalize(input)).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalizeEscapedString() throws Exception {
+    String input =
+        "change: \"jw,lq9F37x/;b\\\\\" --> \"Q;!zio$E\\\"ea,y\\\\a{\", field : \"Q;!zio$E\\\"ea,y\\\\a{\"";
+
+    // Q;!zioEea,y\\a{
+
+    String result =
+        new IdNormalizer(
+                new IncrementingIdProvider(),
+                "id-", //
+                "change: \"("
+                    + IdNormalizer.ESCAPED_STRING
+                    + ")\" --> \""
+                    + IdNormalizer.ESCAPED_STRING
+                    + "\"", //
+                "change: \""
+                    + IdNormalizer.ESCAPED_STRING
+                    + "\" --> \"("
+                    + IdNormalizer.ESCAPED_STRING
+                    + ")\"", //
+                "field : \"(" + IdNormalizer.ESCAPED_STRING + ")\"")
+            .normalize(input);
+
+    assertThat(result).isEqualTo("change: \"id-1\" --> \"id-2\", field : \"id-2\"");
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/normalization/ListOfStringsValidationNormalizerTest.java b/src/test/java/de/cronn/assertions/validationfile/normalization/ListOfStringsValidationNormalizerTest.java
index 37aa17c..eb32c1b 100644
--- a/src/test/java/de/cronn/assertions/validationfile/normalization/ListOfStringsValidationNormalizerTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/normalization/ListOfStringsValidationNormalizerTest.java
@@ -3,23 +3,23 @@
 import static org.assertj.core.api.Assertions.*;
 
 import java.util.Arrays;
-
 import org.junit.jupiter.api.Test;
 
 class ListOfStringsValidationNormalizerTest {
 
-	@Test
-	void testNormalize() throws Exception {
-		// given
-		String input = "Test id-1 and id-2 and id-4";
-		String expected = "Test [valid-id] and [valid-id] and [valid-id]";
+  @Test
+  void testNormalize() throws Exception {
+    // given
+    String input = "Test id-1 and id-2 and id-4";
+    String expected = "Test [valid-id] and [valid-id] and [valid-id]";
 
-		// when
-		String normalized = new CollectionOfStringsValidationNormalizer(
-			Arrays.asList("id-1", "id-2", "id-3", "id-4"), "[valid-id]")
-			.normalize(input);
+    // when
+    String normalized =
+        new CollectionOfStringsValidationNormalizer(
+                Arrays.asList("id-1", "id-2", "id-3", "id-4"), "[valid-id]")
+            .normalize(input);
 
-		// then
-		assertThat(normalized).isEqualTo(expected);
-	}
+    // then
+    assertThat(normalized).isEqualTo(expected);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java b/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java
index 18b2e7f..0d847df 100644
--- a/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/normalization/UuidNormalizerTest.java
@@ -6,43 +6,43 @@
 
 class UuidNormalizerTest {
 
-	@Test
-	void testNormalizeLowercase() {
-		String input = "id=550e8400-e29b-41d4-a716-446655440000";
-		String expected = "id=UUID_1";
+  @Test
+  void testNormalizeLowercase() {
+    String input = "id=550e8400-e29b-41d4-a716-446655440000";
+    String expected = "id=UUID_1";
 
-		assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
-	}
+    assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
+  }
 
-	@Test
-	void testNormalizeUppercase() {
-		String input = "id=550E8400-E29B-41D4-A716-446655440000";
-		String expected = "id=UUID_1";
+  @Test
+  void testNormalizeUppercase() {
+    String input = "id=550E8400-E29B-41D4-A716-446655440000";
+    String expected = "id=UUID_1";
 
-		assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
-	}
+    assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
+  }
 
-	@Test
-	void testNormalizeMixedCase() {
-		String input = "id=550e8400-E29B-41d4-A716-446655440000";
-		String expected = "id=UUID_1";
+  @Test
+  void testNormalizeMixedCase() {
+    String input = "id=550e8400-E29B-41d4-A716-446655440000";
+    String expected = "id=UUID_1";
 
-		assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
-	}
+    assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
+  }
 
-	@Test
-	void testNormalizeMultipleUuids() {
-		String input = "a=550e8400-e29b-41d4-a716-446655440000 b=661f9511-f30c-52e5-b827-557766551111 a=550e8400-e29b-41d4-a716-446655440000";
-		String expected = "a=UUID_1 b=UUID_2 a=UUID_1";
+  @Test
+  void testNormalizeMultipleUuids() {
+    String input =
+        "a=550e8400-e29b-41d4-a716-446655440000 b=661f9511-f30c-52e5-b827-557766551111 a=550e8400-e29b-41d4-a716-446655440000";
+    String expected = "a=UUID_1 b=UUID_2 a=UUID_1";
 
-		assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
-	}
+    assertThat(new UuidNormalizer().normalize(input)).isEqualTo(expected);
+  }
 
-	@Test
-	void testNoUuid() {
-		String input = "no uuids here";
-
-		assertThat(new UuidNormalizer().normalize(input)).isEqualTo(input);
-	}
+  @Test
+  void testNoUuid() {
+    String input = "no uuids here";
 
+    assertThat(new UuidNormalizer().normalize(input)).isEqualTo(input);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilderTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilderTest.java
index 99a0f75..1bfa2f5 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilderTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/AbstractXmlReplacerBuilderTest.java
@@ -2,100 +2,98 @@
 
 import static org.assertj.core.api.Assertions.*;
 
-import org.junit.jupiter.api.Test;
-
 import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
+import org.junit.jupiter.api.Test;
 
 class AbstractXmlReplacerBuilderTest {
-	static class XmlReplacerBuilderForTest extends AbstractXmlReplacerBuilder {
-
-		@Override
-		protected XmlReplacerBuilderForTest getThis() {
-			return this;
-		}
+  static class XmlReplacerBuilderForTest
+      extends AbstractXmlReplacerBuilder {
 
-		@Override
-		protected ValidationNormalizer build(String startTag, String endTag) {
-			throw new RuntimeException("not to be used");
-		}
-	}
+    @Override
+    protected XmlReplacerBuilderForTest getThis() {
+      return this;
+    }
 
-	@Test
-	void testCreateEndTagWithoutNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilderForTest = new XmlReplacerBuilderForTest();
-		xmlReplacerBuilderForTest.withElementName("elem");
+    @Override
+    protected ValidationNormalizer build(String startTag, String endTag) {
+      throw new RuntimeException("not to be used");
+    }
+  }
 
-		String endTag = xmlReplacerBuilderForTest.createEndTag();
+  @Test
+  void testCreateEndTagWithoutNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilderForTest = new XmlReplacerBuilderForTest();
+    xmlReplacerBuilderForTest.withElementName("elem");
 
-		assertThat(endTag).isEqualTo("");
-	}
+    String endTag = xmlReplacerBuilderForTest.createEndTag();
 
-	@Test
-	void testCreateEndTagWithNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilder = new XmlReplacerBuilderForTest()
-			.withNamespace("ns")
-			.withElementName("elem");
+    assertThat(endTag).isEqualTo("");
+  }
 
-		String actual = xmlReplacerBuilder.createEndTag();
+  @Test
+  void testCreateEndTagWithNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilder =
+        new XmlReplacerBuilderForTest().withNamespace("ns").withElementName("elem");
 
-		assertThat(actual).isEqualTo("");
-	}
+    String actual = xmlReplacerBuilder.createEndTag();
 
-	@Test
-	void testCreateStartTagWithNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilder = new XmlReplacerBuilderForTest()
-			.withNamespace("ns")
-			.withElementName("elem");
+    assertThat(actual).isEqualTo("");
+  }
 
-		String startTag = xmlReplacerBuilder.createStartTag();
+  @Test
+  void testCreateStartTagWithNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilder =
+        new XmlReplacerBuilderForTest().withNamespace("ns").withElementName("elem");
 
-		assertThat(startTag).isEqualTo("");
-	}
+    String startTag = xmlReplacerBuilder.createStartTag();
 
-	@Test
-	void testCreateStartTagWithoutNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilder = new XmlReplacerBuilderForTest()
-			.withElementName("elem");
+    assertThat(startTag).isEqualTo("");
+  }
 
-		String startTag = xmlReplacerBuilder.createStartTag();
+  @Test
+  void testCreateStartTagWithoutNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilder =
+        new XmlReplacerBuilderForTest().withElementName("elem");
 
-		assertThat(startTag).isEqualTo("");
-	}
+    String startTag = xmlReplacerBuilder.createStartTag();
 
-	@Test
-	void testCreateStartTagWithAttributeWithoutNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilder = new XmlReplacerBuilderForTest()
-			.withElementName("elem")
-			.withAttribute("attr", "val");
+    assertThat(startTag).isEqualTo("");
+  }
 
-		String startTag = xmlReplacerBuilder.createStartTag();
+  @Test
+  void testCreateStartTagWithAttributeWithoutNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilder =
+        new XmlReplacerBuilderForTest().withElementName("elem").withAttribute("attr", "val");
 
-		assertThat(startTag).isEqualTo("");
-	}
+    String startTag = xmlReplacerBuilder.createStartTag();
 
-	@Test
-	void testCreateStartTagWithAttributeAndNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilder = new XmlReplacerBuilderForTest()
-			.withNamespace("ns")
-			.withElementName("elem")
-			.withAttribute("attr", "val");
+    assertThat(startTag).isEqualTo("");
+  }
 
-		String startTag = xmlReplacerBuilder.createStartTag();
+  @Test
+  void testCreateStartTagWithAttributeAndNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilder =
+        new XmlReplacerBuilderForTest()
+            .withNamespace("ns")
+            .withElementName("elem")
+            .withAttribute("attr", "val");
 
-		assertThat(startTag).isEqualTo("");
-	}
+    String startTag = xmlReplacerBuilder.createStartTag();
 
-	@Test
-	void testCreateStartTagWithMultipleAttributesAndNamespace() throws Exception {
-		XmlReplacerBuilderForTest xmlReplacerBuilder = new XmlReplacerBuilderForTest()
-			.withNamespace("ns")
-			.withElementName("elem")
-			.withAttribute("firstAttr", "firstVal")
-			.withAttribute("secondAttr", "secondVal");
+    assertThat(startTag).isEqualTo("");
+  }
 
-		String startTag = xmlReplacerBuilder.createStartTag();
+  @Test
+  void testCreateStartTagWithMultipleAttributesAndNamespace() throws Exception {
+    XmlReplacerBuilderForTest xmlReplacerBuilder =
+        new XmlReplacerBuilderForTest()
+            .withNamespace("ns")
+            .withElementName("elem")
+            .withAttribute("firstAttr", "firstVal")
+            .withAttribute("secondAttr", "secondVal");
 
-		assertThat(startTag).isEqualTo("");
-	}
+    String startTag = xmlReplacerBuilder.createStartTag();
 
+    assertThat(startTag).isEqualTo("");
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java
index 293be87..f2e2937 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/DateTimeReplacerTest.java
@@ -3,6 +3,7 @@
 import static java.time.format.DateTimeFormatter.*;
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
@@ -12,133 +13,150 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
-
 import org.junit.jupiter.api.Test;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 class DateTimeReplacerTest {
-	@Test
-	void testReturnsInputObjectIfNoMatchIsFound() throws Exception {
-		String nonMatchingString = "doesNotMatch";
-
-		DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("foo (?.+?) bar"),
-			ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
-
-		String normalized = dateTimeReplacer.normalize(nonMatchingString);
-		assertThat(normalized).isSameAs(nonMatchingString);
-	}
-
-	@Test
-	void testReplaceDateTimeStandalone() throws Exception {
-		String dateTime = "2017-09-04T15:39:31.31+02:00";
-		DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("(?.+)"),
-			ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
-
-		String actual = dateTimeReplacer.normalize(dateTime);
-		String expected = "2017-09-04";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testReplaceDateTimeEncapsulated() throws Exception {
-		String dateTime = "foo 2017-09-04T15:39:31.31+02:00 bar";
-		DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("foo (?.+?) bar"),
-			ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
-
-		String actual = dateTimeReplacer.normalize(dateTime);
-		String expected = "foo 2017-09-04 bar";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testReplaceDateTimeEncapsulatedNoMatch() throws Exception {
-		String dateTime = "foo 2017-09-04T15:39:31.31+02:00 bar";
-		DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("foo (?.+?) bar"),
-			DateTimeFormatter.ofPattern("YYYY"), ISO_LOCAL_DATE);
-
-		String actual = dateTimeReplacer.normalize(dateTime);
-		String expected = "foo 2017-09-04T15:39:31.31+02:00 bar";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testReplaceMultipleDateTime() throws Exception {
-		String dateTime = "x 2017-09-04T15:39:31.31+02:00 x " +
-			"x 2016-09-04T15:39:31.31+02:00 x " +
-			"x 2015-09-04T15:39:31.31+02:00 x";
-		DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("x (?.+?) x"),
-			ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
-
-		String actual = dateTimeReplacer.normalize(dateTime);
-		String expected = "x 2017-09-04 x x 2016-09-04 x x 2015-09-04 x";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testReplaceDateTimeInXml() {
-		String xml = "foo 2017-09-04T15:39:31.31+02:00 " +
-			"2017-09-04T15:39:31.31+02:00 bar";
-
-		ValidationNormalizer normalizer = new XmlDateTimeReplacerBuilder().withElementName("dateTime1")
-			.withSourceFormat(ISO_OFFSET_DATE_TIME).withDestinationFormat(ISO_LOCAL_DATE).build();
-
-		String actual = normalizer.normalize(xml);
-		String expected = "foo 2017-09-04 " +
-			"2017-09-04T15:39:31.31+02:00 bar";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalizeIsThreadSafe() throws Exception {
-		DateTimeReplacer dateTimeReplacer = new DateTimeReplacer(Pattern.compile("(?.+)"),
-			ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
-
-		String[] inputs = { "2017-09-04T15:39:31.31+02:00", "2020-12-25T10:00:00.00+02:00" };
-		String[] expectedOutputs = { "2017-09-04", "2020-12-25" };
-
-		int threadCount = 50;
-		CountDownLatch startLatch = new CountDownLatch(1);
-		ExecutorService executor = Executors.newFixedThreadPool(threadCount);
-		List> futures = new ArrayList<>();
-
-		for (int i = 0; i < threadCount; i++) {
-			String input = inputs[i % 2];
-			String expected = expectedOutputs[i % 2];
-			futures.add(executor.submit(() -> {
-				startLatch.await();
-				for (int j = 0; j < 500; j++) {
-					if (!expected.equals(dateTimeReplacer.normalize(input))) {
-						return false;
-					}
-				}
-				return true;
-			}));
-		}
-
-		startLatch.countDown();
-		executor.shutdown();
-		executor.awaitTermination(30, TimeUnit.SECONDS);
-
-		for (Future future : futures) {
-			assertThat(future.get()).isTrue();
-		}
-	}
-
-	@Test
-	void testToString() throws Exception {
-		ValidationNormalizer normalizer = new XmlDateTimeReplacerBuilder().withElementName("date")
-			.withSourceFormat(ISO_OFFSET_DATE_TIME).withDestinationFormat(ISO_LOCAL_DATE).build();
-
-		String expected = "DateTimeReplacer for pattern (?.+?).";
-		String actual = normalizer.toString();
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
+  @Test
+  void testReturnsInputObjectIfNoMatchIsFound() throws Exception {
+    String nonMatchingString = "doesNotMatch";
+
+    DateTimeReplacer dateTimeReplacer =
+        new DateTimeReplacer(
+            Pattern.compile("foo (?.+?) bar"), ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
+
+    String normalized = dateTimeReplacer.normalize(nonMatchingString);
+    assertThat(normalized).isSameAs(nonMatchingString);
+  }
+
+  @Test
+  void testReplaceDateTimeStandalone() throws Exception {
+    String dateTime = "2017-09-04T15:39:31.31+02:00";
+    DateTimeReplacer dateTimeReplacer =
+        new DateTimeReplacer(
+            Pattern.compile("(?.+)"), ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
+
+    String actual = dateTimeReplacer.normalize(dateTime);
+    String expected = "2017-09-04";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testReplaceDateTimeEncapsulated() throws Exception {
+    String dateTime = "foo 2017-09-04T15:39:31.31+02:00 bar";
+    DateTimeReplacer dateTimeReplacer =
+        new DateTimeReplacer(
+            Pattern.compile("foo (?.+?) bar"), ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
+
+    String actual = dateTimeReplacer.normalize(dateTime);
+    String expected = "foo 2017-09-04 bar";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testReplaceDateTimeEncapsulatedNoMatch() throws Exception {
+    String dateTime = "foo 2017-09-04T15:39:31.31+02:00 bar";
+    DateTimeReplacer dateTimeReplacer =
+        new DateTimeReplacer(
+            Pattern.compile("foo (?.+?) bar"),
+            DateTimeFormatter.ofPattern("YYYY"),
+            ISO_LOCAL_DATE);
+
+    String actual = dateTimeReplacer.normalize(dateTime);
+    String expected = "foo 2017-09-04T15:39:31.31+02:00 bar";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testReplaceMultipleDateTime() throws Exception {
+    String dateTime =
+        "x 2017-09-04T15:39:31.31+02:00 x "
+            + "x 2016-09-04T15:39:31.31+02:00 x "
+            + "x 2015-09-04T15:39:31.31+02:00 x";
+    DateTimeReplacer dateTimeReplacer =
+        new DateTimeReplacer(
+            Pattern.compile("x (?.+?) x"), ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
+
+    String actual = dateTimeReplacer.normalize(dateTime);
+    String expected = "x 2017-09-04 x x 2016-09-04 x x 2015-09-04 x";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testReplaceDateTimeInXml() {
+    String xml =
+        "foo 2017-09-04T15:39:31.31+02:00 "
+            + "2017-09-04T15:39:31.31+02:00 bar";
+
+    ValidationNormalizer normalizer =
+        new XmlDateTimeReplacerBuilder()
+            .withElementName("dateTime1")
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(ISO_LOCAL_DATE)
+            .build();
+
+    String actual = normalizer.normalize(xml);
+    String expected =
+        "foo 2017-09-04 "
+            + "2017-09-04T15:39:31.31+02:00 bar";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalizeIsThreadSafe() throws Exception {
+    DateTimeReplacer dateTimeReplacer =
+        new DateTimeReplacer(
+            Pattern.compile("(?.+)"), ISO_OFFSET_DATE_TIME, ISO_LOCAL_DATE);
+
+    String[] inputs = {"2017-09-04T15:39:31.31+02:00", "2020-12-25T10:00:00.00+02:00"};
+    String[] expectedOutputs = {"2017-09-04", "2020-12-25"};
+
+    int threadCount = 50;
+    CountDownLatch startLatch = new CountDownLatch(1);
+    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+    List> futures = new ArrayList<>();
+
+    for (int i = 0; i < threadCount; i++) {
+      String input = inputs[i % 2];
+      String expected = expectedOutputs[i % 2];
+      futures.add(
+          executor.submit(
+              () -> {
+                startLatch.await();
+                for (int j = 0; j < 500; j++) {
+                  if (!expected.equals(dateTimeReplacer.normalize(input))) {
+                    return false;
+                  }
+                }
+                return true;
+              }));
+    }
+
+    startLatch.countDown();
+    executor.shutdown();
+    executor.awaitTermination(30, TimeUnit.SECONDS);
+
+    for (Future future : futures) {
+      assertThat(future.get()).isTrue();
+    }
+  }
+
+  @Test
+  void testToString() throws Exception {
+    ValidationNormalizer normalizer =
+        new XmlDateTimeReplacerBuilder()
+            .withElementName("date")
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(ISO_LOCAL_DATE)
+            .build();
+
+    String expected = "DateTimeReplacer for pattern (?.+?).";
+    String actual = normalizer.toString();
+
+    assertThat(actual).isEqualTo(expected);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilderTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilderTest.java
index e9d44aa..6370760 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilderTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/JsonDateTimeReplacerBuilderTest.java
@@ -3,59 +3,72 @@
 import static java.time.format.DateTimeFormatter.*;
 import static org.assertj.core.api.Assertions.*;
 
-import org.junit.jupiter.api.Test;
-
 import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
+import org.junit.jupiter.api.Test;
 
 class JsonDateTimeReplacerBuilderTest {
 
-	@Test
-	void testReplaceDateTimeInJson() throws Exception {
-		String json = "\"someThing\": \"foo\", \"dateTime1\": \"2017-09-04T15:39:31.31+02:00\", " +
-			"\"dateTime2\": \"2017-09-04T15:39:31.31+02:00\", \"someThing\": \"bar\"";
-
-		ValidationNormalizer normalizer = new JsonDateTimeReplacerBuilder().withKey("dateTime1")
-			.withSourceFormat(ISO_OFFSET_DATE_TIME).withDestinationFormat(ISO_LOCAL_DATE).build();
-
-		String actual = normalizer.normalize(json);
-		String expected = "\"someThing\": \"foo\", \"dateTime1\": \"2017-09-04\", " +
-			"\"dateTime2\": \"2017-09-04T15:39:31.31+02:00\", \"someThing\": \"bar\"";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testReplaceDateTimeInJsonWithCustomColonGroupRegex_match() {
-		String json = "\"dateTime\": \"2017-09-04T15:39:31.31+02:00\"";
-		String colonGroupRegex = "(:\\s)";
-
-		ValidationNormalizer normalizer = new JsonDateTimeReplacerBuilder()
-			.withKey("dateTime").withColonGroupRegex(colonGroupRegex)
-			.withSourceFormat(ISO_OFFSET_DATE_TIME).withDestinationFormat(ISO_LOCAL_DATE).build();
-
-		String expected = "\"dateTime\": \"2017-09-04\"";
-		String actual = normalizer.normalize(json);
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testReplaceDateTimeInJsonWithCustomColonGroupRegex_noMatch() {
-		String json = "\"dateTime\": \"2017-09-04T15:39:31.31+02:00\"";
-		String colonGroupRegex = "(\\s:\\s)";
-
-		ValidationNormalizer normalizer = new JsonDateTimeReplacerBuilder()
-			.withKey("dateTime").withColonGroupRegex(colonGroupRegex)
-			.withSourceFormat(ISO_OFFSET_DATE_TIME).withDestinationFormat(ISO_LOCAL_DATE).build();
-
-		String actual = normalizer.normalize(json);
-
-		assertThat(actual).isEqualTo(json);
-	}
-	@Test
-	void testGetThis() throws Exception {
-		JsonDateTimeReplacerBuilder jsonDateTimeReplacerBuilder = new JsonDateTimeReplacerBuilder();
-		assertThat(jsonDateTimeReplacerBuilder.getThis()).isEqualTo(jsonDateTimeReplacerBuilder);
-	}
-
+  @Test
+  void testReplaceDateTimeInJson() throws Exception {
+    String json =
+        "\"someThing\": \"foo\", \"dateTime1\": \"2017-09-04T15:39:31.31+02:00\", "
+            + "\"dateTime2\": \"2017-09-04T15:39:31.31+02:00\", \"someThing\": \"bar\"";
+
+    ValidationNormalizer normalizer =
+        new JsonDateTimeReplacerBuilder()
+            .withKey("dateTime1")
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(ISO_LOCAL_DATE)
+            .build();
+
+    String actual = normalizer.normalize(json);
+    String expected =
+        "\"someThing\": \"foo\", \"dateTime1\": \"2017-09-04\", "
+            + "\"dateTime2\": \"2017-09-04T15:39:31.31+02:00\", \"someThing\": \"bar\"";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testReplaceDateTimeInJsonWithCustomColonGroupRegex_match() {
+    String json = "\"dateTime\": \"2017-09-04T15:39:31.31+02:00\"";
+    String colonGroupRegex = "(:\\s)";
+
+    ValidationNormalizer normalizer =
+        new JsonDateTimeReplacerBuilder()
+            .withKey("dateTime")
+            .withColonGroupRegex(colonGroupRegex)
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(ISO_LOCAL_DATE)
+            .build();
+
+    String expected = "\"dateTime\": \"2017-09-04\"";
+    String actual = normalizer.normalize(json);
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testReplaceDateTimeInJsonWithCustomColonGroupRegex_noMatch() {
+    String json = "\"dateTime\": \"2017-09-04T15:39:31.31+02:00\"";
+    String colonGroupRegex = "(\\s:\\s)";
+
+    ValidationNormalizer normalizer =
+        new JsonDateTimeReplacerBuilder()
+            .withKey("dateTime")
+            .withColonGroupRegex(colonGroupRegex)
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(ISO_LOCAL_DATE)
+            .build();
+
+    String actual = normalizer.normalize(json);
+
+    assertThat(actual).isEqualTo(json);
+  }
+
+  @Test
+  void testGetThis() throws Exception {
+    JsonDateTimeReplacerBuilder jsonDateTimeReplacerBuilder = new JsonDateTimeReplacerBuilder();
+    assertThat(jsonDateTimeReplacerBuilder.getThis()).isEqualTo(jsonDateTimeReplacerBuilder);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilderTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilderTest.java
index 4cea80d..5f39754 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilderTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/JsonReplacerBuilderTest.java
@@ -2,74 +2,63 @@
 
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.util.stream.Stream;
-
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 class JsonReplacerBuilderTest {
 
-	@Test
-	void testBuildReplacerForGivenKeyReplacingOnlyGivenValue() throws Exception {
-		ValidationNormalizer normalizer = new JsonReplacerBuilder().withKey("key").withStringValue("abcd").build();
-
-		String expected = "\"key\": \"[masked]\", \"key\": \"foobar\"";
-		String actual = normalizer.normalize("\"key\": \"abcd\", \"key\": \"foobar\"");
+  @Test
+  void testBuildReplacerForGivenKeyReplacingOnlyGivenValue() throws Exception {
+    ValidationNormalizer normalizer =
+        new JsonReplacerBuilder().withKey("key").withStringValue("abcd").build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String expected = "\"key\": \"[masked]\", \"key\": \"foobar\"";
+    String actual = normalizer.normalize("\"key\": \"abcd\", \"key\": \"foobar\"");
 
-	@Test
-	void testBuildReplacerWithCustomReplacement() throws Exception {
-		ValidationNormalizer normalizer = new JsonReplacerBuilder().withKey("key").withReplacement("[replaced]").build();
+    assertThat(actual).isEqualTo(expected);
+  }
 
-		String expected = "\"key\": \"[replaced]\", \"other\": \"foobar\"";
-		String actual = normalizer.normalize("\"key\": \"foobar\", \"other\": \"foobar\"");
+  @Test
+  void testBuildReplacerWithCustomReplacement() throws Exception {
+    ValidationNormalizer normalizer =
+        new JsonReplacerBuilder().withKey("key").withReplacement("[replaced]").build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String expected = "\"key\": \"[replaced]\", \"other\": \"foobar\"";
+    String actual = normalizer.normalize("\"key\": \"foobar\", \"other\": \"foobar\"");
 
+    assertThat(actual).isEqualTo(expected);
+  }
 
-	private static Stream testBuildReplacerForDifferentValues() {
-		return Stream.of(
-			Arguments.of(
-				"{\"key\": 312.321, \"key\": 123456789, \"key\": -10.4, \"key\": -4}",
-				"{\"key\": \"[masked]\", \"key\": \"[masked]\", \"key\": \"[masked]\", \"key\": \"[masked]\"}"
-			),
-			Arguments.of(
-				"{\"key\": \"Lorem, ipsum\", \"author\": \"Human\"}",
-				"{\"key\": \"[masked]\", \"author\": \"Human\"}"
-			),
-			Arguments.of(
-				"{\"key\": \"Lorem, ipsum. {not object} [not array] More text \\\"Quote\\\" single \\\" \", \"author\": \"Human\"}",
-				"{\"key\": \"[masked]\", \"author\": \"Human\"}"
-			),
-			Arguments.of(
-				"{\"string\": \"Lorem, ipsum\", \"key\": \"Human\"}",
-				"{\"string\": \"Lorem, ipsum\", \"key\": \"[masked]\"}"
-			),
-			Arguments.of(
-				"{\"string\": \"Lorem, ipsum\", \"key\": 1000}",
-				"{\"string\": \"Lorem, ipsum\", \"key\": \"[masked]\"}"
-			),
-			Arguments.of(
-				"\"key\": \"null\"",
-				"\"key\": \"[masked]\""),
-			Arguments.of(
-				"\"key\": true, \"key\": false",
-				"\"key\": \"[masked]\", \"key\": \"[masked]\""
-			)
-		);
-	}
+  private static Stream testBuildReplacerForDifferentValues() {
+    return Stream.of(
+        Arguments.of(
+            "{\"key\": 312.321, \"key\": 123456789, \"key\": -10.4, \"key\": -4}",
+            "{\"key\": \"[masked]\", \"key\": \"[masked]\", \"key\": \"[masked]\", \"key\": \"[masked]\"}"),
+        Arguments.of(
+            "{\"key\": \"Lorem, ipsum\", \"author\": \"Human\"}",
+            "{\"key\": \"[masked]\", \"author\": \"Human\"}"),
+        Arguments.of(
+            "{\"key\": \"Lorem, ipsum. {not object} [not array] More text \\\"Quote\\\" single \\\" \", \"author\": \"Human\"}",
+            "{\"key\": \"[masked]\", \"author\": \"Human\"}"),
+        Arguments.of(
+            "{\"string\": \"Lorem, ipsum\", \"key\": \"Human\"}",
+            "{\"string\": \"Lorem, ipsum\", \"key\": \"[masked]\"}"),
+        Arguments.of(
+            "{\"string\": \"Lorem, ipsum\", \"key\": 1000}",
+            "{\"string\": \"Lorem, ipsum\", \"key\": \"[masked]\"}"),
+        Arguments.of("\"key\": \"null\"", "\"key\": \"[masked]\""),
+        Arguments.of(
+            "\"key\": true, \"key\": false", "\"key\": \"[masked]\", \"key\": \"[masked]\""));
+  }
 
-	@ParameterizedTest
-	@MethodSource
-	void testBuildReplacerForDifferentValues(String toNormalize, String expected) {
-		ValidationNormalizer normalizer = new JsonReplacerBuilder().withKey("key").build();
-		assertThat(normalizer.normalize(toNormalize)).isEqualTo(expected);
-	}
+  @ParameterizedTest
+  @MethodSource
+  void testBuildReplacerForDifferentValues(String toNormalize, String expected) {
+    ValidationNormalizer normalizer = new JsonReplacerBuilder().withKey("key").build();
+    assertThat(normalizer.normalize(toNormalize)).isEqualTo(expected);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/ReplacerTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/ReplacerTest.java
index 1bd3d96..7a0259d 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/ReplacerTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/ReplacerTest.java
@@ -3,63 +3,60 @@
 import static org.assertj.core.api.Assertions.*;
 
 import java.util.regex.Pattern;
-
 import org.junit.jupiter.api.Test;
 
 class ReplacerTest {
 
-	@Test
-	void testNormalizeAlreadyCompiledPattern() throws Exception {
-		Replacer replacer = new Replacer(Pattern.compile("a[b]*?c"), "replacement");
-
-		String expected = "replacement, replacement, ab, a, c, bc";
-		String actual = replacer.normalize("abc, ac, ab, a, c, bc");
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testNormalize() throws Exception {
-		Replacer replacer = new Replacer("a[b]*?c", "replacement");
-
-		String expected = "replacement, replacement, ab, a, c, bc";
-		String actual = replacer.normalize("abc, ac, ab, a, c, bc");
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testForXml() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = Replacer.forXml();
-		assertThat(xmlReplacerBuilder).isNotNull();
-	}
-
-	@Test
-	void testForXmlDateTime() throws Exception {
-		XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = Replacer.forXmlDateTime();
-		assertThat(xmlDateTimeReplacerBuilder).isNotNull();
-	}
-
-	@Test
-	void testForJson() throws Exception {
-		JsonReplacerBuilder jsonReplacerBuilder = Replacer.forJson();
-		assertThat(jsonReplacerBuilder).isNotNull();
-	}
-
-	@Test
-	void testForJsonDateTime() throws Exception {
-		JsonDateTimeReplacerBuilder jsonDateTimeReplacerBuilder = Replacer.forJsonDateTime();
-		assertThat(jsonDateTimeReplacerBuilder).isNotNull();
-	}
-
-	@Test
-	void testToString() throws Exception {
-		Replacer replacer = new Replacer(Pattern.compile("a[b]c"), "a");
-		String expected = "Replacer for pattern a[b]c with replacement a.";
-		String actual = replacer.toString();
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-
+  @Test
+  void testNormalizeAlreadyCompiledPattern() throws Exception {
+    Replacer replacer = new Replacer(Pattern.compile("a[b]*?c"), "replacement");
+
+    String expected = "replacement, replacement, ab, a, c, bc";
+    String actual = replacer.normalize("abc, ac, ab, a, c, bc");
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testNormalize() throws Exception {
+    Replacer replacer = new Replacer("a[b]*?c", "replacement");
+
+    String expected = "replacement, replacement, ab, a, c, bc";
+    String actual = replacer.normalize("abc, ac, ab, a, c, bc");
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testForXml() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = Replacer.forXml();
+    assertThat(xmlReplacerBuilder).isNotNull();
+  }
+
+  @Test
+  void testForXmlDateTime() throws Exception {
+    XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = Replacer.forXmlDateTime();
+    assertThat(xmlDateTimeReplacerBuilder).isNotNull();
+  }
+
+  @Test
+  void testForJson() throws Exception {
+    JsonReplacerBuilder jsonReplacerBuilder = Replacer.forJson();
+    assertThat(jsonReplacerBuilder).isNotNull();
+  }
+
+  @Test
+  void testForJsonDateTime() throws Exception {
+    JsonDateTimeReplacerBuilder jsonDateTimeReplacerBuilder = Replacer.forJsonDateTime();
+    assertThat(jsonDateTimeReplacerBuilder).isNotNull();
+  }
+
+  @Test
+  void testToString() throws Exception {
+    Replacer replacer = new Replacer(Pattern.compile("a[b]c"), "a");
+    String expected = "Replacer for pattern a[b]c with replacement a.";
+    String actual = replacer.toString();
+
+    assertThat(actual).isEqualTo(expected);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/XmlDateTimeReplacerBuilderTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/XmlDateTimeReplacerBuilderTest.java
index e8f423e..4320d21 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/XmlDateTimeReplacerBuilderTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/XmlDateTimeReplacerBuilderTest.java
@@ -3,62 +3,67 @@
 import static java.time.format.DateTimeFormatter.*;
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import java.time.format.DateTimeFormatter;
-
 import org.junit.jupiter.api.Test;
 
-import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
-
 class XmlDateTimeReplacerBuilderTest {
 
-	@Test
-	void testBuildXMLDateTimeReplacer() {
-		XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
-		ValidationNormalizer normalizer = xmlDateTimeReplacerBuilder.withElementName("elem").withSourceFormat(ISO_OFFSET_DATE_TIME)
-			.withDestinationFormat(DateTimeFormatter.ofPattern("YYYY")).build();
-
-		String actual = normalizer.normalize("2011-12-03T10:15:30+01:00");
-		String expected = "2011";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testBuildXMLDateTimeReplacerWithExactDateToMatch() {
-		String dateToMatch = "2011-12-03T10:15:30+01:00";
-		XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
-		ValidationNormalizer normalizer = xmlDateTimeReplacerBuilder
-			.withElementName("elem")
-			.withContent(dateToMatch)
-			.withSourceFormat(ISO_OFFSET_DATE_TIME)
-			.withDestinationFormat(DateTimeFormatter.ofPattern("YYYY")).build();
-
-		String actual = normalizer.normalize("2011-12-03T10:15:30+01:00");
-		String expected = "2011";
-
-		assertThat(actual).isEqualTo(expected);
-	}
-
-	@Test
-	void testBuildXMLDateTimeReplacerWithExactDateToMatch_noMatch() {
-		String dateToMatch = "2017-12-03T10:15:30+01:00";
-		XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
-		ValidationNormalizer normalizer = xmlDateTimeReplacerBuilder
-			.withElementName("elem")
-			.withContent(dateToMatch)
-			.withSourceFormat(ISO_OFFSET_DATE_TIME)
-			.withDestinationFormat(DateTimeFormatter.ofPattern("YYYY")).build();
-
-		String source = "2011-12-03T10:15:30+01:00";
-		String actual = normalizer.normalize(source);
-
-		assertThat(actual).isEqualTo(source);
-	}
-
-	@Test
-	void testGetThis() throws Exception {
-		XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
-		assertThat(xmlDateTimeReplacerBuilder.getThis()).isEqualTo(xmlDateTimeReplacerBuilder);
-	}
-
+  @Test
+  void testBuildXMLDateTimeReplacer() {
+    XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlDateTimeReplacerBuilder
+            .withElementName("elem")
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(DateTimeFormatter.ofPattern("YYYY"))
+            .build();
+
+    String actual = normalizer.normalize("2011-12-03T10:15:30+01:00");
+    String expected = "2011";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testBuildXMLDateTimeReplacerWithExactDateToMatch() {
+    String dateToMatch = "2011-12-03T10:15:30+01:00";
+    XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlDateTimeReplacerBuilder
+            .withElementName("elem")
+            .withContent(dateToMatch)
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(DateTimeFormatter.ofPattern("YYYY"))
+            .build();
+
+    String actual = normalizer.normalize("2011-12-03T10:15:30+01:00");
+    String expected = "2011";
+
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  void testBuildXMLDateTimeReplacerWithExactDateToMatch_noMatch() {
+    String dateToMatch = "2017-12-03T10:15:30+01:00";
+    XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlDateTimeReplacerBuilder
+            .withElementName("elem")
+            .withContent(dateToMatch)
+            .withSourceFormat(ISO_OFFSET_DATE_TIME)
+            .withDestinationFormat(DateTimeFormatter.ofPattern("YYYY"))
+            .build();
+
+    String source = "2011-12-03T10:15:30+01:00";
+    String actual = normalizer.normalize(source);
+
+    assertThat(actual).isEqualTo(source);
+  }
+
+  @Test
+  void testGetThis() throws Exception {
+    XmlDateTimeReplacerBuilder xmlDateTimeReplacerBuilder = new XmlDateTimeReplacerBuilder();
+    assertThat(xmlDateTimeReplacerBuilder.getThis()).isEqualTo(xmlDateTimeReplacerBuilder);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilderTest.java b/src/test/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilderTest.java
index 06f73a2..c775d70 100644
--- a/src/test/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilderTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/replacements/XmlReplacerBuilderTest.java
@@ -2,93 +2,96 @@
 
 import static org.assertj.core.api.Assertions.*;
 
-import org.junit.jupiter.api.Test;
-
 import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
+import org.junit.jupiter.api.Test;
 
 class XmlReplacerBuilderTest {
 
-	@Test
-	void testWithContent() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").withContent("abcd").build();
-
-		String actual = normalizer.normalize("abcd");
-		String expected = "[masked]";
+  @Test
+  void testWithContent() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlReplacerBuilder.withElementName("elem").withContent("abcd").build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String actual = normalizer.normalize("abcd");
+    String expected = "[masked]";
 
-	@Test
-	void testDefaultContentMatchesAnything() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").build();
+    assertThat(actual).isEqualTo(expected);
+  }
 
-		String actual = normalizer.normalize("123 \\ sdf abcd");
-		String expected = "[masked]";
+  @Test
+  void testDefaultContentMatchesAnything() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String actual = normalizer.normalize("123 \\ sdf abcd");
+    String expected = "[masked]";
 
-	@Test
-	void testDefaultContentMatchesAnythingButLineBreaks() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").build();
+    assertThat(actual).isEqualTo(expected);
+  }
 
-		String source = "123 \\ sdf \n abcd";
-		String actual = normalizer.normalize(source);
+  @Test
+  void testDefaultContentMatchesAnythingButLineBreaks() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").build();
 
-		assertThat(actual).isEqualTo(source);
-	}
+    String source = "123 \\ sdf \n abcd";
+    String actual = normalizer.normalize(source);
 
-	@Test
-	void testWithReplacement() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").withReplacement("[replaced]").build();
+    assertThat(actual).isEqualTo(source);
+  }
 
-		String actual = normalizer.normalize("abcd");
-		String expected = "[replaced]";
+  @Test
+  void testWithReplacement() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlReplacerBuilder.withElementName("elem").withReplacement("[replaced]").build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String actual = normalizer.normalize("abcd");
+    String expected = "[replaced]";
 
-	@Test
-	void testWithSomeContent() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").withSomeContent().build();
+    assertThat(actual).isEqualTo(expected);
+  }
 
-		String actual = normalizer.normalize("abcd");
-		String expected = "[masked]";
+  @Test
+  void testWithSomeContent() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlReplacerBuilder.withElementName("elem").withSomeContent().build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String actual = normalizer.normalize("abcd");
+    String expected = "[masked]";
 
-	@Test
-	void testWithSomeDigitsContent() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").withSomeDigitsContent().build();
+    assertThat(actual).isEqualTo(expected);
+  }
 
-		String actual = normalizer.normalize("12345");
-		String expected = "[masked]";
+  @Test
+  void testWithSomeDigitsContent() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlReplacerBuilder.withElementName("elem").withSomeDigitsContent().build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String actual = normalizer.normalize("12345");
+    String expected = "[masked]";
 
-	@Test
-	void testWithSomeDigitsContentDoesNotMatchNonDigitContent() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		ValidationNormalizer normalizer = xmlReplacerBuilder.withElementName("elem").withSomeDigitsContent().build();
+    assertThat(actual).isEqualTo(expected);
+  }
 
-		String actual = normalizer.normalize("abcd");
-		String expected = "abcd";
+  @Test
+  void testWithSomeDigitsContentDoesNotMatchNonDigitContent() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    ValidationNormalizer normalizer =
+        xmlReplacerBuilder.withElementName("elem").withSomeDigitsContent().build();
 
-		assertThat(actual).isEqualTo(expected);
-	}
+    String actual = normalizer.normalize("abcd");
+    String expected = "abcd";
 
-	@Test
-	void testGetThis() throws Exception {
-		XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
-		assertThat(xmlReplacerBuilder).isEqualTo(xmlReplacerBuilder.getThis());
-	}
+    assertThat(actual).isEqualTo(expected);
+  }
 
+  @Test
+  void testGetThis() throws Exception {
+    XmlReplacerBuilder xmlReplacerBuilder = new XmlReplacerBuilder();
+    assertThat(xmlReplacerBuilder).isEqualTo(xmlReplacerBuilder.getThis());
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/sample/DummySerializer.java b/src/test/java/de/cronn/assertions/validationfile/sample/DummySerializer.java
index e92237a..8069190 100644
--- a/src/test/java/de/cronn/assertions/validationfile/sample/DummySerializer.java
+++ b/src/test/java/de/cronn/assertions/validationfile/sample/DummySerializer.java
@@ -1,53 +1,52 @@
 package de.cronn.assertions.validationfile.sample;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.util.StdDateFormat;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.stream.Collectors;
-
 import org.junit.platform.commons.util.ReflectionUtils;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.util.StdDateFormat;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-
 final class DummySerializer {
 
-	static ObjectMapper objectMapper = new ObjectMapper()
-		.registerModule(new JavaTimeModule())
-		.setDateFormat(new StdDateFormat())
-		.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);
-
-	private DummySerializer() {
-	}
-
-	static String toCsvString(Object obj) {
-		Map properties = properties(obj);
-		String header = String.join(",", properties.keySet());
-		String values = properties.values().stream()
-			.map(String::valueOf)
-			.collect(Collectors.joining(","));
-		return header + "\n" + values;
-	}
-
-	static String toJsonString(Object obj) throws IOException {
-		return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
-	}
-
-	static String toXmlString(Object obj) {
-		String className = obj.getClass().getSimpleName();
-		return properties(obj).entrySet().stream()
-			.map(entry -> String.format("<%1$s>%2$s", entry.getKey(), entry.getValue()))
-			.collect(Collectors.joining("\n", "<" + className + ">\n", "\n"));
-	}
-
-	private static Map properties(Object obj) {
-		return ReflectionUtils.findMethods(obj.getClass(), method -> method.getName().startsWith("get"))
-			.stream()
-			.collect(Collectors
-				.toMap(method -> method.getName().substring(3), method -> ReflectionUtils.invokeMethod(method, obj),
-					(a, b) -> a,
-					LinkedHashMap::new));
-	}
+  static ObjectMapper objectMapper =
+      new ObjectMapper()
+          .registerModule(new JavaTimeModule())
+          .setDateFormat(new StdDateFormat())
+          .setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);
+
+  private DummySerializer() {}
+
+  static String toCsvString(Object obj) {
+    Map properties = properties(obj);
+    String header = String.join(",", properties.keySet());
+    String values =
+        properties.values().stream().map(String::valueOf).collect(Collectors.joining(","));
+    return header + "\n" + values;
+  }
+
+  static String toJsonString(Object obj) throws IOException {
+    return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
+  }
+
+  static String toXmlString(Object obj) {
+    String className = obj.getClass().getSimpleName();
+    return properties(obj).entrySet().stream()
+        .map(entry -> String.format("<%1$s>%2$s", entry.getKey(), entry.getValue()))
+        .collect(Collectors.joining("\n", "<" + className + ">\n", "\n"));
+  }
+
+  private static Map properties(Object obj) {
+    return ReflectionUtils.findMethods(obj.getClass(), method -> method.getName().startsWith("get"))
+        .stream()
+        .collect(
+            Collectors.toMap(
+                method -> method.getName().substring(3),
+                method -> ReflectionUtils.invokeMethod(method, obj),
+                (a, b) -> a,
+                LinkedHashMap::new));
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/sample/SampleStructure.java b/src/test/java/de/cronn/assertions/validationfile/sample/SampleStructure.java
index 5c4a216..2e526fe 100644
--- a/src/test/java/de/cronn/assertions/validationfile/sample/SampleStructure.java
+++ b/src/test/java/de/cronn/assertions/validationfile/sample/SampleStructure.java
@@ -8,68 +8,74 @@
 
 class SampleStructure {
 
-	private String name;
-	private String messageId;
-	private Long transactionId;
-	private LocalDateTime timestamp;
+  private String name;
+  private String messageId;
+  private Long transactionId;
+  private LocalDateTime timestamp;
 
-	public static SampleStructure filledWithConstantValues() {
-		SampleStructure sampleStructure = new SampleStructure();
-		sampleStructure.setName("Lorem ipsum");
-		sampleStructure.setMessageId("fb8ca74b-c7a3-4031-8250-bec29280e9ad");
-		sampleStructure.setTransactionId(7047158660679727112L);
-		sampleStructure.setTimestamp(LocalDateTime.of(2020, 02, 02, 12, 20, 30));
-		return sampleStructure;
-	}
+  public static SampleStructure filledWithConstantValues() {
+    SampleStructure sampleStructure = new SampleStructure();
+    sampleStructure.setName("Lorem ipsum");
+    sampleStructure.setMessageId("fb8ca74b-c7a3-4031-8250-bec29280e9ad");
+    sampleStructure.setTransactionId(7047158660679727112L);
+    sampleStructure.setTimestamp(LocalDateTime.of(2020, 02, 02, 12, 20, 30));
+    return sampleStructure;
+  }
 
-	public static SampleStructure filledWithRandomValues() {
-		SampleStructure sampleStructure = new SampleStructure();
-		sampleStructure.setName("Lorem ipsum");
-		sampleStructure.setMessageId(UUID.randomUUID().toString());
-		sampleStructure.setTransactionId(Math.abs(new Random().nextLong()));
-		sampleStructure.setTimestamp(LocalDateTime.of(LocalDate.of(2020, 02, 02), LocalTime.now()));
-		return sampleStructure;
-	}
+  public static SampleStructure filledWithRandomValues() {
+    SampleStructure sampleStructure = new SampleStructure();
+    sampleStructure.setName("Lorem ipsum");
+    sampleStructure.setMessageId(UUID.randomUUID().toString());
+    sampleStructure.setTransactionId(Math.abs(new Random().nextLong()));
+    sampleStructure.setTimestamp(LocalDateTime.of(LocalDate.of(2020, 02, 02), LocalTime.now()));
+    return sampleStructure;
+  }
 
-	public String getName() {
-		return name;
-	}
+  public String getName() {
+    return name;
+  }
 
-	public void setName(String name) {
-		this.name = name;
-	}
+  public void setName(String name) {
+    this.name = name;
+  }
 
-	public String getMessageId() {
-		return messageId;
-	}
+  public String getMessageId() {
+    return messageId;
+  }
 
-	public void setMessageId(String messageId) {
-		this.messageId = messageId;
-	}
+  public void setMessageId(String messageId) {
+    this.messageId = messageId;
+  }
 
-	public Long getTransactionId() {
-		return transactionId;
-	}
+  public Long getTransactionId() {
+    return transactionId;
+  }
 
-	public void setTransactionId(Long transactionId) {
-		this.transactionId = transactionId;
-	}
+  public void setTransactionId(Long transactionId) {
+    this.transactionId = transactionId;
+  }
 
-	public LocalDateTime getTimestamp() {
-		return timestamp;
-	}
+  public LocalDateTime getTimestamp() {
+    return timestamp;
+  }
 
-	public void setTimestamp(LocalDateTime timestamp) {
-		this.timestamp = timestamp;
-	}
+  public void setTimestamp(LocalDateTime timestamp) {
+    this.timestamp = timestamp;
+  }
 
-	@Override
-	public String toString() {
-		return "SampleStructure{" +
-			"name='" + name + '\'' +
-			", messageId='" + messageId + '\'' +
-			", transactionId=" + transactionId +
-			", timestamp=" + timestamp +
-			'}';
-	}
+  @Override
+  public String toString() {
+    return "SampleStructure{"
+        + "name='"
+        + name
+        + '\''
+        + ", messageId='"
+        + messageId
+        + '\''
+        + ", transactionId="
+        + transactionId
+        + ", timestamp="
+        + timestamp
+        + '}';
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest.java b/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest.java
index 2f5bb98..e4ff246 100644
--- a/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest.java
@@ -2,77 +2,104 @@
 
 import static java.time.format.DateTimeFormatter.*;
 
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeFormatterBuilder;
-import java.util.regex.Pattern;
-
-import org.junit.jupiter.api.Test;
-
 import de.cronn.assertions.validationfile.FileExtensions;
 import de.cronn.assertions.validationfile.junit5.JUnit5ValidationFileAssertions;
 import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
 import de.cronn.assertions.validationfile.replacements.DateTimeReplacer;
 import de.cronn.assertions.validationfile.replacements.Replacer;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
 
 class ValidationFileSampleTest implements JUnit5ValidationFileAssertions {
 
-	@Test
-	void simpleComparison() throws Exception {
-		String actualString = "this will be content of /data/test/validation/ValidationFileSample_simpleComparison.txt";
-		assertWithFile(actualString);
-	}
-
-	@Test
-	void differentFileSuffixes() throws Exception {
-		assertWithFileWithSuffix("this will be content of ValidationFileSample_differentFileSuffixes_example1.txt", "example1");
-		assertWithFileWithSuffix("this will be content of ValidationFileSample_differentFileSuffixes_example2.txt", "example2");
-	}
-
-	@Test
-	void differentFileFormats() throws Exception {
-		SampleStructure sampleStructure = SampleStructure.filledWithConstantValues();
-		assertWithJsonFile(DummySerializer.toJsonString(sampleStructure));
-		assertWithXmlFile(DummySerializer.toXmlString(sampleStructure));
-		assertWithFile(DummySerializer.toCsvString(sampleStructure), FileExtensions.CSV);
-		assertWithFile(sampleStructure.toString(), () -> "str.txt");
-	}
-
-	@Test
-	void normalization_json() throws Exception {
-		SampleStructure sampleStructure = SampleStructure.filledWithRandomValues();
-
-		assertWithJsonFile(DummySerializer.toJsonString(sampleStructure), ValidationNormalizer.combine(
-			Replacer.forJson().withKey("MessageId").build(),
-			Replacer.forJson().withKey("TransactionId").withRawValue("\\d+").withReplacement("[transaction-id]").build(),
-			Replacer.forJsonDateTime().withKey("Timestamp").withSourceFormat(ISO_DATE_TIME).withDestinationFormat(normalizedIsoLocalDate()).build()
-		));
-	}
-
-	@Test
-	void normalization_xml() throws Exception {
-		SampleStructure sampleStructure = SampleStructure.filledWithRandomValues();
-
-		assertWithXmlFile(DummySerializer.toXmlString(sampleStructure), ValidationNormalizer.combine(
-			Replacer.forXml().withElementName("MessageId").build(),
-			Replacer.forXml().withElementName("TransactionId").withContent("\\d+").withReplacement("[transaction-id]").build(),
-			Replacer.forXmlDateTime().withElementName("Timestamp").withSourceFormat(ISO_DATE_TIME).withDestinationFormat(normalizedIsoLocalDate()).build()
-		));
-	}
-
-	@Test
-	void normalization_custom() throws Exception {
-		SampleStructure sampleStructure = SampleStructure.filledWithRandomValues();
-
-		assertWithFile(sampleStructure.toString(), ValidationNormalizer.combine(
-			new Replacer("(messageId)='[\\w-]{36}'", "$1=[masked]"),
-			new Replacer("(transactionId)=\\d+", "$1=[transaction-id]"),
-			new DateTimeReplacer(Pattern.compile("timestamp=(?[\\d-]+T[\\d:]+\\.\\d+)"), ISO_DATE_TIME, normalizedIsoLocalDate())
-		));
-	}
-
-	private DateTimeFormatter normalizedIsoLocalDate() {
-		return new DateTimeFormatterBuilder()
-			.append(ISO_LOCAL_DATE).appendLiteral("T").appendLiteral("[masked]").toFormatter();
-	}
-
+  @Test
+  void simpleComparison() throws Exception {
+    String actualString =
+        "this will be content of /data/test/validation/ValidationFileSample_simpleComparison.txt";
+    assertWithFile(actualString);
+  }
+
+  @Test
+  void differentFileSuffixes() throws Exception {
+    assertWithFileWithSuffix(
+        "this will be content of ValidationFileSample_differentFileSuffixes_example1.txt",
+        "example1");
+    assertWithFileWithSuffix(
+        "this will be content of ValidationFileSample_differentFileSuffixes_example2.txt",
+        "example2");
+  }
+
+  @Test
+  void differentFileFormats() throws Exception {
+    SampleStructure sampleStructure = SampleStructure.filledWithConstantValues();
+    assertWithJsonFile(DummySerializer.toJsonString(sampleStructure));
+    assertWithXmlFile(DummySerializer.toXmlString(sampleStructure));
+    assertWithFile(DummySerializer.toCsvString(sampleStructure), FileExtensions.CSV);
+    assertWithFile(sampleStructure.toString(), () -> "str.txt");
+  }
+
+  @Test
+  void normalization_json() throws Exception {
+    SampleStructure sampleStructure = SampleStructure.filledWithRandomValues();
+
+    assertWithJsonFile(
+        DummySerializer.toJsonString(sampleStructure),
+        ValidationNormalizer.combine(
+            Replacer.forJson().withKey("MessageId").build(),
+            Replacer.forJson()
+                .withKey("TransactionId")
+                .withRawValue("\\d+")
+                .withReplacement("[transaction-id]")
+                .build(),
+            Replacer.forJsonDateTime()
+                .withKey("Timestamp")
+                .withSourceFormat(ISO_DATE_TIME)
+                .withDestinationFormat(normalizedIsoLocalDate())
+                .build()));
+  }
+
+  @Test
+  void normalization_xml() throws Exception {
+    SampleStructure sampleStructure = SampleStructure.filledWithRandomValues();
+
+    assertWithXmlFile(
+        DummySerializer.toXmlString(sampleStructure),
+        ValidationNormalizer.combine(
+            Replacer.forXml().withElementName("MessageId").build(),
+            Replacer.forXml()
+                .withElementName("TransactionId")
+                .withContent("\\d+")
+                .withReplacement("[transaction-id]")
+                .build(),
+            Replacer.forXmlDateTime()
+                .withElementName("Timestamp")
+                .withSourceFormat(ISO_DATE_TIME)
+                .withDestinationFormat(normalizedIsoLocalDate())
+                .build()));
+  }
+
+  @Test
+  void normalization_custom() throws Exception {
+    SampleStructure sampleStructure = SampleStructure.filledWithRandomValues();
+
+    assertWithFile(
+        sampleStructure.toString(),
+        ValidationNormalizer.combine(
+            new Replacer("(messageId)='[\\w-]{36}'", "$1=[masked]"),
+            new Replacer("(transactionId)=\\d+", "$1=[transaction-id]"),
+            new DateTimeReplacer(
+                Pattern.compile("timestamp=(?[\\d-]+T[\\d:]+\\.\\d+)"),
+                ISO_DATE_TIME,
+                normalizedIsoLocalDate())));
+  }
+
+  private DateTimeFormatter normalizedIsoLocalDate() {
+    return new DateTimeFormatterBuilder()
+        .append(ISO_LOCAL_DATE)
+        .appendLiteral("T")
+        .appendLiteral("[masked]")
+        .toFormatter();
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest_ShowFailures.java b/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest_ShowFailures.java
index e86b0d5..79e270a 100644
--- a/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest_ShowFailures.java
+++ b/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileSampleTest_ShowFailures.java
@@ -1,20 +1,20 @@
 package de.cronn.assertions.validationfile.sample;
 
-import org.junit.jupiter.api.Disabled;
-
 import de.cronn.assertions.validationfile.normalization.ValidationNormalizer;
+import org.junit.jupiter.api.Disabled;
 
 @Disabled("Enable to demonstrate how fails work")
 public class ValidationFileSampleTest_ShowFailures extends ValidationFileSampleTest {
 
-	@Override
-	public String getTestName() {
-		return super.getTestName().replace("_ShowFailures", "");
-	}
+  @Override
+  public String getTestName() {
+    return super.getTestName().replace("_ShowFailures", "");
+  }
 
-	@Override
-	public void assertWithFile(String actualOutput, String filename, ValidationNormalizer normalizer) {
-		actualOutput = "extra text\n" + actualOutput;
-		super.assertWithFile(actualOutput, filename, null);
-	}
+  @Override
+  public void assertWithFile(
+      String actualOutput, String filename, ValidationNormalizer normalizer) {
+    actualOutput = "extra text\n" + actualOutput;
+    super.assertWithFile(actualOutput, filename, null);
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileWithSoftAssertionsSampleTest.java b/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileWithSoftAssertionsSampleTest.java
index 32a2d81..a627803 100644
--- a/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileWithSoftAssertionsSampleTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/sample/ValidationFileWithSoftAssertionsSampleTest.java
@@ -1,5 +1,6 @@
 package de.cronn.assertions.validationfile.sample;
 
+import de.cronn.assertions.validationfile.junit5.JUnit5ValidationFileAssertions;
 import org.assertj.core.api.SoftAssertions;
 import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
 import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
@@ -7,24 +8,20 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
-import de.cronn.assertions.validationfile.junit5.JUnit5ValidationFileAssertions;
-
 @Disabled("Enable to demonstrate how soft assertions work")
 @ExtendWith(SoftAssertionsExtension.class)
 class ValidationFileWithSoftAssertionsSampleTest implements JUnit5ValidationFileAssertions {
 
-	@InjectSoftAssertions
-	private SoftAssertions softly;
-
-	@Override
-	public FailedAssertionHandler failedAssertionHandler() {
-		return callable -> softly.check(callable::call);
-	}
+  @InjectSoftAssertions private SoftAssertions softly;
 
-	@Test
-	void testSoftAssertions() throws Exception {
-		assertWithFileWithSuffix("actual1", "file1");
-		assertWithFileWithSuffix("actual2", "file2");
-	}
+  @Override
+  public FailedAssertionHandler failedAssertionHandler() {
+    return callable -> softly.check(callable::call);
+  }
 
+  @Test
+  void testSoftAssertions() throws Exception {
+    assertWithFileWithSuffix("actual1", "file1");
+    assertWithFileWithSuffix("actual2", "file2");
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsDataProviderTest.java b/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsDataProviderTest.java
index 6e0b95d..a7f641a 100644
--- a/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsDataProviderTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsDataProviderTest.java
@@ -1,10 +1,9 @@
 package de.cronn.assertions.validationfile.util;
 
-
 import static org.junit.jupiter.params.provider.Arguments.*;
 
+import de.cronn.assertions.validationfile.ValidationFileAssertions;
 import java.util.stream.Stream;
-
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -12,50 +11,35 @@
 import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import de.cronn.assertions.validationfile.ValidationFileAssertions;
-
 class FileBasedComparisonUtilsDataProviderTest implements ValidationFileAssertions {
 
-	private TestInfo testInfo;
-
-	@BeforeEach
-	void init(TestInfo testInfo) {
-		this.testInfo = testInfo;
-	}
-
-	private static Stream objectDataProvider() {
-		return Stream.of(
-			arguments("one!", "2016-01-01T00:00:00.123456Z"),
-			arguments("two+", "2016-01-01")
-		);
-	}
-
-	@ParameterizedTest
-	@ValueSource(strings = { "one!", "t \nw*o+" })
-	void shouldCheckAgainstValidationFile(String input) throws Exception {
-		assertWithFileWithSuffix(
-			input,
-			input
-		);
-	}
-
-	@ParameterizedTest
-	@MethodSource("objectDataProvider")
-	void shouldCheckAgainstValidationFile(String input, Object secondValue) throws Exception {
-		assertWithFileWithSuffix(
-			String.format("%sX%s", input, secondValue),
-			input + "_" + secondValue
-		);
-	}
-
-	@Override
-	public String getTestName() {
-		return TestNameUtils.getTestName(
-			getClass(),
-			testInfo.getTestMethod()
-				.orElseThrow(IllegalStateException::new)
-				.getName()
-		);
-	}
-
+  private TestInfo testInfo;
+
+  @BeforeEach
+  void init(TestInfo testInfo) {
+    this.testInfo = testInfo;
+  }
+
+  private static Stream objectDataProvider() {
+    return Stream.of(
+        arguments("one!", "2016-01-01T00:00:00.123456Z"), arguments("two+", "2016-01-01"));
+  }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"one!", "t \nw*o+"})
+  void shouldCheckAgainstValidationFile(String input) throws Exception {
+    assertWithFileWithSuffix(input, input);
+  }
+
+  @ParameterizedTest
+  @MethodSource("objectDataProvider")
+  void shouldCheckAgainstValidationFile(String input, Object secondValue) throws Exception {
+    assertWithFileWithSuffix(String.format("%sX%s", input, secondValue), input + "_" + secondValue);
+  }
+
+  @Override
+  public String getTestName() {
+    return TestNameUtils.getTestName(
+        getClass(), testInfo.getTestMethod().orElseThrow(IllegalStateException::new).getName());
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsTest.java b/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsTest.java
index b1b3c67..f0e0d8e 100644
--- a/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/util/FileBasedComparisonUtilsTest.java
@@ -2,111 +2,122 @@
 
 import static org.assertj.core.api.Assertions.*;
 
+import de.cronn.assertions.validationfile.FileBasedComparisonFailure;
+import de.cronn.assertions.validationfile.extension.ValidationFilesTestHelper;
 import java.io.IOException;
 import java.nio.file.Paths;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-import de.cronn.assertions.validationfile.FileBasedComparisonFailure;
-import de.cronn.assertions.validationfile.extension.ValidationFilesTestHelper;
-
 class FileBasedComparisonUtilsTest {
 
-	@RegisterExtension
-	static ValidationFilesTestHelper helper = new ValidationFilesTestHelper();
-
-	@AfterEach
-	void tearDown() throws IOException {
-		helper.cleanupFiles();
-	}
-
-	@Test
-	void testRunForTheFirstTime_throwsError() throws Exception {
-		// Given
-		assertThat(helper.collectAddedFiles()).isEmpty();
-
-		// When
-		Throwable error = catchThrowable(
-			() -> FileBasedComparisonUtils.compareActualWithFileHidden("text", "file.txt", null)
-		);
-
-		// Then
-		assertThat(error).hasMessage("\n" +
-			"--- expected/file.txt\n" +
-			"+++ actual/file.txt\n" +
-			"@@ -1,2 +1,1 @@\n" +
-			"-=== new file \"data/test/validation/file.txt\" ===\n" +
-			" text");
-		assertThat(helper.collectAddedFiles()).containsExactlyInAnyOrder(
-			Paths.get("data/test/validation/file.txt"),
-			Paths.get("data/test/output/file.txt"),
-			Paths.get("data/test/tmp/file.txt.raw")
-		);
-		assertThat(helper.linesDiffOutputValidation("file.txt"))
-			.containsExactly("-=== new file \"data/test/validation/file.txt\" ===");
-	}
-
-	@Test
-	void testActualOutputHasChanged_throwsError() throws Throwable {
-		// Given
-		createPrefilledValidationFile("text", "file.txt");
-
-		// When
-		Throwable error = catchThrowable(
-			() -> FileBasedComparisonUtils.compareActualWithFileHidden("text\nextra line", "file.txt", null)
-		);
-
-		// Then
-		assertThat(error).hasMessage("\n" +
-			"--- expected/file.txt\n" +
-			"+++ actual/file.txt\n" +
-			"@@ -1,1 +1,2 @@\n" +
-			" text\n" +
-			"+extra line");
-		assertThat(helper.linesDiffOutputValidation("file.txt"))
-			.containsExactly("+extra line");
-	}
-
-	@Test
-	void testCopyingOutputFileToValidationPath_fixesError() throws Throwable {
-		// Given
-		createPrefilledValidationFile("text", "file.txt");
-		assertThatThrownBy(
-			() -> FileBasedComparisonUtils.compareActualWithFileHidden("text\nextra line", "file.txt", null)
-		).isInstanceOf(FileBasedComparisonFailure.class);
-
-		// When
-		helper.copyOutputToValidation("file.txt");
-
-		// Then
-		assertThatCode(
-			() -> FileBasedComparisonUtils.compareActualWithFileHidden("text\nextra line", "file.txt", null)
-		).doesNotThrowAnyException();
-		assertThat(helper.linesDiffOutputValidation("file.txt")).isEmpty();
-	}
-
-	@Test
-	void testReplaceCharactersThatAreForbiddenInWindowsFileNames() throws Exception {
-		final String someAllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabzdefghijklmnopqrstuvwxyz0123456789.-_/";
-		assertThat(FileBasedComparisonUtils.validateAndFixFilename(someAllowedCharacters)).isEqualTo(someAllowedCharacters);
-
-		assertThat(FileBasedComparisonUtils.validateAndFixFilename("some/path_with_back\\slash.ext"))
-			.isEqualTo("some/path_with_back_slash.ext");
-
-		assertThat(FileBasedComparisonUtils.validateAndFixFilename("<>:\"|?*")).isEqualTo("_______");
-		assertThat(FileBasedComparisonUtils.validateAndFixFilename("\u0000\u0001\u0002\u0003\u0004\u0005" +
-			"\u0006\u0007\u0008\u0009\u000b\u000c\n\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017" +
-			"\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f")).isEqualTo("_______________________________");
-	}
-
-	private void createPrefilledValidationFile(String actualOutput, String validationFileName) throws IOException {
-		try {
-			FileBasedComparisonUtils.compareActualWithFileHidden(actualOutput, validationFileName, null);
-		} catch (FileBasedComparisonFailure failure) {
-			helper.copyOutputToValidation(validationFileName);
-		}
-	}
-
+  @RegisterExtension static ValidationFilesTestHelper helper = new ValidationFilesTestHelper();
+
+  @AfterEach
+  void tearDown() throws IOException {
+    helper.cleanupFiles();
+  }
+
+  @Test
+  void testRunForTheFirstTime_throwsError() throws Exception {
+    // Given
+    assertThat(helper.collectAddedFiles()).isEmpty();
+
+    // When
+    Throwable error =
+        catchThrowable(
+            () -> FileBasedComparisonUtils.compareActualWithFileHidden("text", "file.txt", null));
+
+    // Then
+    assertThat(error)
+        .hasMessage(
+            "\n"
+                + "--- expected/file.txt\n"
+                + "+++ actual/file.txt\n"
+                + "@@ -1,2 +1,1 @@\n"
+                + "-=== new file \"data/test/validation/file.txt\" ===\n"
+                + " text");
+    assertThat(helper.collectAddedFiles())
+        .containsExactlyInAnyOrder(
+            Paths.get("data/test/validation/file.txt"),
+            Paths.get("data/test/output/file.txt"),
+            Paths.get("data/test/tmp/file.txt.raw"));
+    assertThat(helper.linesDiffOutputValidation("file.txt"))
+        .containsExactly("-=== new file \"data/test/validation/file.txt\" ===");
+  }
+
+  @Test
+  void testActualOutputHasChanged_throwsError() throws Throwable {
+    // Given
+    createPrefilledValidationFile("text", "file.txt");
+
+    // When
+    Throwable error =
+        catchThrowable(
+            () ->
+                FileBasedComparisonUtils.compareActualWithFileHidden(
+                    "text\nextra line", "file.txt", null));
+
+    // Then
+    assertThat(error)
+        .hasMessage(
+            "\n"
+                + "--- expected/file.txt\n"
+                + "+++ actual/file.txt\n"
+                + "@@ -1,1 +1,2 @@\n"
+                + " text\n"
+                + "+extra line");
+    assertThat(helper.linesDiffOutputValidation("file.txt")).containsExactly("+extra line");
+  }
+
+  @Test
+  void testCopyingOutputFileToValidationPath_fixesError() throws Throwable {
+    // Given
+    createPrefilledValidationFile("text", "file.txt");
+    assertThatThrownBy(
+            () ->
+                FileBasedComparisonUtils.compareActualWithFileHidden(
+                    "text\nextra line", "file.txt", null))
+        .isInstanceOf(FileBasedComparisonFailure.class);
+
+    // When
+    helper.copyOutputToValidation("file.txt");
+
+    // Then
+    assertThatCode(
+            () ->
+                FileBasedComparisonUtils.compareActualWithFileHidden(
+                    "text\nextra line", "file.txt", null))
+        .doesNotThrowAnyException();
+    assertThat(helper.linesDiffOutputValidation("file.txt")).isEmpty();
+  }
+
+  @Test
+  void testReplaceCharactersThatAreForbiddenInWindowsFileNames() throws Exception {
+    final String someAllowedCharacters =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabzdefghijklmnopqrstuvwxyz0123456789.-_/";
+    assertThat(FileBasedComparisonUtils.validateAndFixFilename(someAllowedCharacters))
+        .isEqualTo(someAllowedCharacters);
+
+    assertThat(FileBasedComparisonUtils.validateAndFixFilename("some/path_with_back\\slash.ext"))
+        .isEqualTo("some/path_with_back_slash.ext");
+
+    assertThat(FileBasedComparisonUtils.validateAndFixFilename("<>:\"|?*")).isEqualTo("_______");
+    assertThat(
+            FileBasedComparisonUtils.validateAndFixFilename(
+                "\u0000\u0001\u0002\u0003\u0004\u0005"
+                    + "\u0006\u0007\u0008\u0009\u000b\u000c\n\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017"
+                    + "\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"))
+        .isEqualTo("_______________________________");
+  }
+
+  private void createPrefilledValidationFile(String actualOutput, String validationFileName)
+      throws IOException {
+    try {
+      FileBasedComparisonUtils.compareActualWithFileHidden(actualOutput, validationFileName, null);
+    } catch (FileBasedComparisonFailure failure) {
+      helper.copyOutputToValidation(validationFileName);
+    }
+  }
 }
diff --git a/src/test/java/de/cronn/assertions/validationfile/util/MarkdownTableTest.java b/src/test/java/de/cronn/assertions/validationfile/util/MarkdownTableTest.java
index 7da17bb..08b2cce 100644
--- a/src/test/java/de/cronn/assertions/validationfile/util/MarkdownTableTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/util/MarkdownTableTest.java
@@ -2,50 +2,48 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import de.cronn.assertions.validationfile.FileExtensions;
+import de.cronn.assertions.validationfile.junit5.JUnit5ValidationFileAssertions;
 import java.util.Arrays;
 import java.util.List;
-
 import org.junit.jupiter.api.Test;
 
-import de.cronn.assertions.validationfile.FileExtensions;
-import de.cronn.assertions.validationfile.junit5.JUnit5ValidationFileAssertions;
-
 class MarkdownTableTest implements JUnit5ValidationFileAssertions {
 
-	@Test
-	void shouldBuildMarkdownInManyWays() {
-		List header = Arrays.asList("Title", "First Name", "Surname", "Company");
-		MarkdownTable markdownTable = new MarkdownTable(header);
-
-		markdownTable.addRow("Mr", "George", "Dawson", "Countrywide Powdered Coatings Ltd");
-		markdownTable.addRow(Arrays.asList("Mrs", "Stacy", "Felderman", "On Time Workwear Ltd"));
-
-		markdownTable.addCells("Mr", "Scott", "Zuckerman");
-		markdownTable.addCell("Zuckerman Security Ltd");
-		markdownTable.nextRow();
-
-		markdownTable.addCells(Arrays.asList("Mrs", "Isabelle", "Hewitt", "Craft Textiles Ltd"));
-		markdownTable.nextRow();
-
-		markdownTable.addRow("Mr", "Albert", "Barker", "Barker Insulation Services Ltd");
-
-		assertWithFile(markdownTable.toString(), FileExtensions.MD);
-	}
-
-	@Test
-	void shouldPadCells() {
-		MarkdownTable markdownTable = new MarkdownTable();
-		markdownTable.addRow("Company");
-		markdownTable.addRow("On Time Workwear Ltd");
-		markdownTable.addRow("Countrywide Powdered Coatings Ltd");
-		markdownTable.addRow("Craft Textiles Ltd");
-
-		assertThat(markdownTable.toString())
-			.isEqualTo("| Company                           |\n" +
-				"|-----------------------------------|\n" +
-				"| On Time Workwear Ltd              |\n" +
-				"| Countrywide Powdered Coatings Ltd |\n" +
-				"| Craft Textiles Ltd                |");
-	}
-
-}
\ No newline at end of file
+  @Test
+  void shouldBuildMarkdownInManyWays() {
+    List header = Arrays.asList("Title", "First Name", "Surname", "Company");
+    MarkdownTable markdownTable = new MarkdownTable(header);
+
+    markdownTable.addRow("Mr", "George", "Dawson", "Countrywide Powdered Coatings Ltd");
+    markdownTable.addRow(Arrays.asList("Mrs", "Stacy", "Felderman", "On Time Workwear Ltd"));
+
+    markdownTable.addCells("Mr", "Scott", "Zuckerman");
+    markdownTable.addCell("Zuckerman Security Ltd");
+    markdownTable.nextRow();
+
+    markdownTable.addCells(Arrays.asList("Mrs", "Isabelle", "Hewitt", "Craft Textiles Ltd"));
+    markdownTable.nextRow();
+
+    markdownTable.addRow("Mr", "Albert", "Barker", "Barker Insulation Services Ltd");
+
+    assertWithFile(markdownTable.toString(), FileExtensions.MD);
+  }
+
+  @Test
+  void shouldPadCells() {
+    MarkdownTable markdownTable = new MarkdownTable();
+    markdownTable.addRow("Company");
+    markdownTable.addRow("On Time Workwear Ltd");
+    markdownTable.addRow("Countrywide Powdered Coatings Ltd");
+    markdownTable.addRow("Craft Textiles Ltd");
+
+    assertThat(markdownTable.toString())
+        .isEqualTo(
+            "| Company                           |\n"
+                + "|-----------------------------------|\n"
+                + "| On Time Workwear Ltd              |\n"
+                + "| Countrywide Powdered Coatings Ltd |\n"
+                + "| Craft Textiles Ltd                |");
+  }
+}
diff --git a/src/test/java/de/cronn/assertions/validationfile/util/TestNameUtilsTest.java b/src/test/java/de/cronn/assertions/validationfile/util/TestNameUtilsTest.java
index 3f64da3..a773ff3 100644
--- a/src/test/java/de/cronn/assertions/validationfile/util/TestNameUtilsTest.java
+++ b/src/test/java/de/cronn/assertions/validationfile/util/TestNameUtilsTest.java
@@ -6,18 +6,21 @@
 
 class TestNameUtilsTest {
 
-	@Test
-	void testByExample_getTestName() {
-		assertThat(TestNameUtils.getTestName(TestNameUtilsTest.class, "testName_suffix")).isEqualTo("TestNameUtilsTest_testName_suffix");
-		assertThat(TestNameUtils.getTestName(TestNameUtilsTest.class, "_testName_suffix")).isEqualTo("TestNameUtilsTest_testName_suffix");
-		assertThat(TestNameUtils.getTestName(Object.class, "testName")).isEqualTo("Object_testName");
+  @Test
+  void testByExample_getTestName() {
+    assertThat(TestNameUtils.getTestName(TestNameUtilsTest.class, "testName_suffix"))
+        .isEqualTo("TestNameUtilsTest_testName_suffix");
+    assertThat(TestNameUtils.getTestName(TestNameUtilsTest.class, "_testName_suffix"))
+        .isEqualTo("TestNameUtilsTest_testName_suffix");
+    assertThat(TestNameUtils.getTestName(Object.class, "testName")).isEqualTo("Object_testName");
 
-		assertThat(TestNameUtils.getTestName(NestedClass.class, "testName")).isEqualTo("TestNameUtilsTest_NestedClass_testName");
-		assertThat(TestNameUtils.getTestName(NestedClass.NestedNestedClass.class, "testName")).isEqualTo("TestNameUtilsTest_NestedClass_NestedNestedClass_testName");
-	}
+    assertThat(TestNameUtils.getTestName(NestedClass.class, "testName"))
+        .isEqualTo("TestNameUtilsTest_NestedClass_testName");
+    assertThat(TestNameUtils.getTestName(NestedClass.NestedNestedClass.class, "testName"))
+        .isEqualTo("TestNameUtilsTest_NestedClass_NestedNestedClass_testName");
+  }
 
-	class NestedClass {
-		class NestedNestedClass {
-		}
-	}
+  class NestedClass {
+    class NestedNestedClass {}
+  }
 }

From 97c086784a7fd3ddd9337f9996d40c197588ec2c Mon Sep 17 00:00:00 2001
From: Michal Bazanski 
Date: Thu, 7 May 2026 09:31:18 +0200
Subject: [PATCH 10/11] Upgrade Gradle wrapper to 9.5.0

---
 build.gradle.kts                         |   8 +-
 gradle.lockfile                          |   6 +-
 gradle/wrapper/gradle-wrapper.jar        | Bin 59203 -> 48462 bytes
 gradle/wrapper/gradle-wrapper.properties |   6 +-
 gradlew                                  | 283 ++++++++++++++---------
 gradlew.bat                              |  59 +++--
 6 files changed, 209 insertions(+), 153 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 9713a7d..2a94c58 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -14,10 +14,6 @@ java {
     targetCompatibility = JavaVersion.VERSION_1_8
     withSourcesJar()
     withJavadocJar()
-
-    registerFeature("junit5Support") {
-        usingSourceSet(sourceSets["main"])
-    }
 }
 
 tasks.withType {
@@ -25,7 +21,7 @@ tasks.withType {
 }
 
 dependencies {
-    "junit5SupportImplementation"("org.junit.jupiter:junit-jupiter-api:[5.0,6.0)")
+    compileOnly("org.junit.jupiter:junit-jupiter-api:[5.0,6.0)")
     api("com.googlecode.java-diff-utils:diffutils:latest.release")
     api("org.opentest4j:opentest4j:latest.release")
 
@@ -139,7 +135,7 @@ spotless {
 }
 
 tasks.wrapper {
-    gradleVersion = "8.10.2"
+    gradleVersion = "9.5.0"
     distributionType = Wrapper.DistributionType.ALL
 }
 
diff --git a/gradle.lockfile b/gradle.lockfile
index 51dd379..a9224f1 100644
--- a/gradle.lockfile
+++ b/gradle.lockfile
@@ -10,13 +10,13 @@ com.googlecode.java-diff-utils:diffutils:1.3.0=compileClasspath,runtimeClasspath
 net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath
 org.apiguardian:apiguardian-api:1.1.2=compileClasspath,testCompileClasspath
 org.assertj:assertj-core:3.27.7=testCompileClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:5.14.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.jupiter:junit-jupiter-api:5.14.4=compileClasspath,testCompileClasspath,testRuntimeClasspath
 org.junit.jupiter:junit-jupiter-engine:5.14.4=testRuntimeClasspath
 org.junit.jupiter:junit-jupiter-params:5.14.4=testCompileClasspath,testRuntimeClasspath
 org.junit.jupiter:junit-jupiter:5.14.4=testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-commons:1.14.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.platform:junit-platform-commons:1.14.4=compileClasspath,testCompileClasspath,testRuntimeClasspath
 org.junit.platform:junit-platform-engine:1.14.4=testRuntimeClasspath
 org.junit.platform:junit-platform-launcher:1.14.4=testRuntimeClasspath
-org.junit:junit-bom:5.14.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit:junit-bom:5.14.4=compileClasspath,testCompileClasspath,testRuntimeClasspath
 org.opentest4j:opentest4j:1.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 empty=annotationProcessor,signatures,testAnnotationProcessor
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644
GIT binary patch
literal 48462
zcma&NV{|3jwk;gnwr$(CRk3Z`Sy9Ed?Nn^ruGlsztklcC=e7I2x9>aqJFB(1eyu-q
z%|3b`eLzVT6buar3JMAc2#EOW{C^)LAZQ?YaW!FjX$1*JIcZUG1yyl%HEd!6f#E+}*Jo*NafvM<-FbE0;-_L#rp}qdn%JEoAVNlEB#J^Oq`mU_#*ev4HLmc>
zjXz_hFft^><#omb;Zer-%wm4hxo!wjuX3hBldg(^-RiOleKin`>KHfL3P*{k?(rji(#j2Cc0K509#>qu=-T&B!-5EBi(+
zIuTD-qfcAYgS@`Fb2^-p)4#o6A3z0&fp?~cV=CRsAeCmO4ZQ5kKgC%0el=Q&Rhd#k
zaGmAbUW8uKC}-C0s~2);d{;mpsNBx9rn__66W{AhaSvJEK+c0b6ARO+l(CI7E|S5x
zhaYP--@F<|99X&)9`q^2(^-Zu^Tzfm)v|gkTJHQ!G*zIg5hzoygeXZoYUEJ;iFkE#
zq^r$*c|>Hmn3GapzcDYnjgSFiO^NFyTR5AH#mh%zRToMpEi(r)1$5)h455DuV}0al
z!*psWuL@Ke-2gvftfMEGf9YEi^<{B@qru
zINgo+YsE&LN?)1qItJoNhISp-fZ86`XR#*6xcvM~_7=JHUX;K9*=Gu5X~
zix|O2d=&C#u_w{=B$eCpJ4L*6i7={j+{Og~`Emz@&98}6s<-p^)`0fXE4cJBP{>)Ltb>JwcqI>yz
z0-r-SEhC@p)XOoh|1|XgjFaREHfsu4dAGVz*k#m+V<4
zHqvlud6=;#QWHUoTR_a8Y8+heN?M%n1@0YLiaN@GuOPNd26tik7eKulTx?mM-R!1H
znB6+H{^krFXg_b{y=QeCT~qR3T4}l+b!Oz9;~|3*6F<3?#|DYYW&1RtFE)ILZ!`85
zVmvrZkLTzf31unH7Cc5E0iFShqlBE9hgEnRJH1juII*vyp&xd!g`q}X_6WT6E$hhQ`Vdp9k^<)VS?lj!cTh
z7FQcQAVA@jL^cXod8cnhKG2TS9+;QU6Kq>}UOY3&TL9gXbl{Fv8@WsF=z7>X0To@$
zY@Oi1uc|MdJ$>Kn{@!g_e`-I&Tpwfg9cr>(iakDX1qciCG_1y!Di#4_)lE!bWJbrp
z5aUonb6m-?tiQyR_`P#~SOu+tb_ev6JO>EbEhHK@KbeT0_FDo>dl9bMg)>xmCNB*g
zG5NC8ABavuTEZVGW6jP*nAqRt3W?7Iigc-EE~zpNJXRAE
z>`~RO9$892j&I1kV;9U)xT8^}IeV`n{}QDtj2o-RBt`DGZUOO;O*lFCb_vpyGh*;95PfeGu!dyrmZ9VJ3Z*upg
z6R-3Lr%_55$Hw1^{+KWx0#z`T7O6sXo1h;m?B_ur`X2bFz-SzDrL
zpk^@B<+I6imc@7vip
za%1jMB7q@1j#
zz{u?YojZMW{5j$@h=v4iu2mTu7IzI|)Sxn!74=*J>1a&?Xjt
z2%JhSi#4huEcD9qdR9Lj4vwmfnL{%+vQ{f-KgYeqin(OPd8+(g*Uq#TLxQjD4
zLCL%ul(V&PAPlAx8D`@K8Rc`{GPecQ<)d=KWel0ejFeeXGQ6o7601B!!I@RY&eDriADD6wP6DcFKDLZ|lO#YwnrNCZ)zRJpdxX_nPZa4j#$j6v!h|6p!dH}MY6#B`@%6=)
z-HigguDACKBULnon^FKzazF|Y1{t(U5rUGnEU|}djVsWT-F>@@mNx?_$kF51QF4C5
zStKR$^3(fw85(4HGs9{mUTtn1)3PwxTN?6}j;32&vJ^BiPHfndLkdU5sOemXKGyCZ
z@<7j(k>DNeo~QXyJkFWk!7(y1SB%nA3{v~P2c8ooKa4auM!el!Q_=;lJ$c5ADqE+^
zX8*|A99v;jWPrm(8=h;2ZAj|(vVbx~wQ{N%v;eYLD_BB2LAEWCs@xauyBDl(_HIBvA(XJ7B1E;O
zJYCJ8xFJh7f5sr;Y#Wp_`$4Z_H4e9bGiBp?Qu&2!@%Bl2dT5evfFO*^hLDiBu2%Jl
z*WAlL5PaQ7skJa(qVysky}DQquZ8U?2@UyJ8zB#=U_E>MgE%XA$CtfL31m$rATJvC
zs@!crc0=128PM=Zp
zW_5Czv9))n_8Ru?{pxM2F8^r%*O41}RnONbSj*piG%`nyF>6ky=|;B&k8iot(J=kyoU3p<_zaAX(1ijzf*uXA
zZ_5jeC{Lks+&QeFIlmzZi3+fsF4fNW^~kvC4Q*T-vrNP!x9xnen12lZQM=1_MdW76LKX(GuW`%T~dM^YX6+ras|Xy4Qhfcq=D+z-P-ea
z`T;^gj3+grr3^hwqcNTJErl$z+k>{bYFm6QV%7Opth?9+>|Dn)O@`7F@=j-XSqGPW
zjUAu%b3Er@;j1%RZxVDhI3sakg-gvTLOSV7;FV6ED=(5;UG??=WADZw^=$4AyFh#}VMe3afM^pF
zFa}-nM8X=K?Jy02*o02@6k{
z%O!hBhjXlXKdhy3A{xGB<##e|j3^dFv~~%v2_H{t(mN7NVeS~51?D&Ozbxa`qwZ_4
z;C#Q#fL1sua%ggucgIEHZtcY=Ag&GgE|h7Q{77D!WUq`;SSGEE0pU;aoj<7-JCAvf
zduN=(tx3Mb+EUXKoax|v;8b@#HJ&Q|!g4ryrl|R>WlAv?IH`bk)I24;eE4NIq@SLK31LD4+w~#3iN{=<`<1R!t^$@K5>U6%W=%8_ANuR5
zs(IDuI18ftirTDARnGmF%;iz+4{MlMihJw_l!0Y)NttXC_t+s)V<EY>=Xin*nGX79k6vQ?beRk
zy_J>@YSC_gMIG$yjO-y&o>S6xtfT27aSs>e|`x(f2R1bM}*518~%x>1Yct=18b&Z>GiS*>VB$+i2876zL)1cT
zN33g=g|>xWE2)dds5m2+8Vy)m-u@NHOlGYxxjam21r1;xWtT0TgqKZrl}*LSkqFt4
zNTI1=3o%C*!-i;iWnlca$stRdwITA1?#fD~5OIqIQAM18BwO_u>hqL&OAANiF|8rG
z_IZ9mp?FA-{Gq9+Ky<#NgL1gWJixfO0ziP$4T4G>vsvqC-NQh+A64F4!
z-(t<=AbPSG%`mTl6BJtH~3RmvPhQlE-EUkEoBIP(_WMN
zK~Fe!siee{M*ns1hkp5(2}vX#%u+T!Abh=<_gEx_QW?h4V@B>uOCEetEe01tl)^`V
z(=cOLmuOB;8&&m%_6pcyrt83UXkJ`f9I&0KxY09}RTTs!l^_7~8$tPA%Hm#&$k0;#
zF;O0zCGo0IN)X~SyKDoY1DW{Ulce|V9w=ld;U`z$t$>8U!Gu8V?_LAJAudt3eI#*!
z2i9~F=kP5m>!bmb%1e~b1!1gz01Py(Yw5gOsFN#o1a&d|=PpgN(#UVreY9^99I0iG
zaYE@>(C^V7pnoB~#w$2C1_TIb1N5Je&iao?S2A*TF>@vpHg`31{uk<9{zf_}s&z%dL-Fo)C$yl$%pAdqU!HJgp
zh_{m1imk{&{ScyeuziqZHu5cto0{S}^BlXu%
z0~;>_yHGd#?Kt8ErxK)z6ojj5SacQobw)-8`c!$HOI*V6eyqou{1Upm%_p!BY^t(D
zDtn(oQ!jff`ddGSD;P8Hes!v)OKW-*>mS&#i0ow87;h>(=Cu0>b4)|=EegbN5=Xkh
z9Ge13=3z#sk+fT<)PuUUf_%Nx@l!P?t*mni^94p^Ax6b2SVL5U>9dHH!H4DL4}@?@
z?Gpq$C**OmWliYA{5s<|EZ@QI2{-K#brFxfA~AIqq&-WSALHWQ8}%mvaNFasrtnE{
zg=sB4-RF!?)nf{>Wo~kNFgYefoFHBcSr*;iF9B!R=5Np|jv>Uf+mcarG-XGy*kP{z
zISVyoPcl_9cOg-@613Qx16OGF#sH&2NTHDa_}vyidmxS~pMfY#AeQvu?AXpWNzi7A
z*6&7a7!C9HRU+N{>WYTh0GXoBnXw{lQby^XShgDOw@e8TP}9Y*oFV4MVF#@Ds2A+A
zXBEt3a@-IIl)TOcXx;0P;|ihR%Tq@DXeG5p-O{!T7Sg$s1
z8OA4iOx-!>6eK^x{jU-0SvByimK|nZik5zKIvvWVGE)4=x^&5Nx%Qgje!k3VoizaB
zip#?$u(R8u{wUFC>tVR8oA%7fs?xEu(gYn>y6BB%vwPR9&RoZE%%RK!
zl#Qnkl^+Y*Y4L{Xk(YX&aGj|zSpqO_;C3CTepA!L#4EXO|(eA`Fi+2EQ3!C
zo^SpVP?{chQ3uaxu7y>w213e22cdA#l-M2kStPE%sq6vE4M*?3At!S7tIp(tQg(Ml
zECjeJw8)*#LYYk_+Txv3rxsH9jJZBRrHp29yJ(^;_PEdn%#U1q`r89}38;XeF{ee&
zsZEsUbJ{LtwOjU{vjL(Wvs2!Bx;#^Mzld&TjS@oo3kk=0P36MC-Ie6eHNN&{8b^s
z0@jcbdejrrj!>r#Wu=3H1dgjeOI}NkhmE}K+UK&M>%7b!n&{0Zixk%^)6#@=V~IZN
zxG>9kl&STQth}qScidfg58d2dF|v_U<@+V^eE@$4x;7oS3)MvWusA?9+%rN>aY#eA_6
zic@S(@e9$9tQM-&-7>X8~#n{5G}nuOu=dSyN+b~jA;_SExZ1H9Q1A}}Rz;XtXUIOP0~
zZzS|~T+%de-nGI$s?wxaJoe+99vmo%xm8o8SNEsAqAE)4LNvHc-1AX24C4k4u3vZmov^_VcxgGxapV(8)_K(^8=
z2d{xCrmk(x&514Ly?e{Mf6}h3=oeP7+ZE{%B^c-kK8g0W{tYw3q%zty_Rd@1nbnyHMwabNp-sSyzpV4v>QsnKcQjF67%g~n&3t^1MesVxCzfJ5b=SOI#YfPP^^JGQw=9L1RCMFbrU{8O0LWOUdBK#j&{`tzXX
zpe2_{+-8$a+o#%8MUlL4$yK`*--z&3{@Y?jP!m{g5nM+Ht=bD3o}Ok~sBQ_!^!->!
z?NDVtyLXzmGYCEmjSCDK*q?Aq1;8fz9l9|z@~l{)R6GfKELc^(nV+TjjI^n0M+S0i
z@YOu*Tk>|M6a0_n$(E;#^1Zgif<-CpYiMvyT+Y*9Z?&~IKSwsLa5Q#p_?FqK3lKIw
zlp6Hk%lio6)yq>m-`QT2Nj-q!aX7~Hlm^Xh6FNbw
z$#ri(Kk*GUHXORu@`aYQU@
zB~S-oIO^~abRPocemkm!W73dbb!j^_xgo_@#W#6p12>w^{){VfeX?U71Xyn9&E
zHa1#*!4c;?r}jv7dMN`g#&R_S215)dccDOJr=uz%LIz@zia+LIFjRakROr?P
zQ|Xw0Pa8o7&W=fw17`+SqepsQ-Os5v3ncD5|N?N(AHH&`>hLY+CLOluJ
z_ErpaT49zK(UcdNmQ%iA-`jS`A_1c|$W86{d_T_T2V-HH3xUqpX0QJSH%i>1i>#vK
z&y{;5)^pMB=u;&_DEWakQU>j&+opIrBf~2GUh{`kG{|Z&2Z}5dwG}>Y{W_uQHaR$_
zYH%}$c`CGC-FGCetRdQ@RZ2-%ucC_|R?mHzYEnqC%u9zRBH8wx7po`=EVPMpq+hL2
zTdjVhQn$)++17^cn;<3=bxJy0Z$U;i3AqJMPJO&SuieU&0eVX?eLEEI7Av@#PV_ZQ
zsa>I>B5HE996O$z6HyJfhEt^aC><@AnzeN`xs@lv>^pPFtcodrcGyqPSB?#C`Piu0
zh5=hAW|OtT9hs*G?7}@*mG_f7ae@-Nz4{qvne66kco^uD$(JbCo2ttqUm-SMy@kx%
z!eDt?5>w5)M!E#C!b#Iu9GqyhUs|QoYWHtR{4espRS-LUt=viY2iygF=-j3kcU#uF
z{ka2=zsOuLR}s;&PbbrB`zty&NfZpV*Y;~i*W$EH0JOGS&FMS%VK@)f*%OOrcU3P9
zq4zjhMpx}oc`PWtP!o5Bdlp=(A***TZwVwuZbuB1Pibv5uiHvW{PsE-k5IfCgUz~l
z0nMeZU0R>(ajoQ0G%Il)z0BgRR*bsdz5NcqJ<)niF6|PUO0i}<4)q>6wx4K(5>Y_I
z4$WMkbCOQFs(krBnl
zx85i0*7%Zm(&nKNP?AQ}d~6@?D9dO%@}ouN2paSR;zyUqJuw)1SRy=g%o;g(BD|Bh
ztnKV(4fcBgDJ~M@%}n-6ow3xOhnC>C^d?PbS(9=TnO)k5p+W;pu2F4eiG7ts
zJVL4M(NiZPQDy*9`H>-P0GWY#=UTnh8feiNF}hCs`8^ZDKy;XIL^9K4Ps&y^#DQSE
z-?J
z@YOQ9NQi>ZP>^ix5K`R07kWj?`R(B?E*OyR1$Vd;8p%2Y2zEYt4CJM~gVX%MO(E1B
zzXhsHn~R1ifq9~dtzuH!*3&W;r`D(Sjrc)m#EI%`Car;CMWcU0c+0r?O!)HpjEvyP
zb^;pO-Bn6e-+>dS^o{q&8yEH9v}vuXX`W;NPRlwJdX|59`z?~z{pFE!^u{3k{KkJ55^
zD;F0ldy9W*`d5YP|0(E6|K%}9|D^SIq>wO)4^cJ+yCa&xl*3}hpvcQ1eP_k;@>tz=
zOZnw)#fxHc81jPcTM#)jgy|0?n0(jd3IPu-lJ&Tm`#F1)o$GTwYp@dlqy-qiHFCHS
zKgikMUx|%x=_%B)>n_y^+HvD2=nP`}-G_0A7)I$yc4`tXS-On8qOkNp>Q^$|Ew%Jm
zYx34*(*Z3SF}xw$CA?nG9O3ZH7l)@Dp4EyH>8eXDb}AFz)k*T53iA~gRu&e15u@|%
z9Rw?69nQOeJhv^^unjd-VGFwbDzf9K{i(U{xxHyM@-aI+0qP{TU0G~w+Fs>taL#Ik
z4+92(Z7n%+okd478;__0GkE`&(C`k8h@?UNnM=F%A~2|TKo)q9F<5`s)KwxJRw~k;
z4giS~|8AIVG;rde6I^W6m9fliR^7YT*>&x7wv^?xu(5p45n{|2F>x%?9Jq+~Tqo9#
zChbeGm@9!(s;uIKae_4h@`~yIj`Tqct+-M>d>~2PCiQ?UmFUioyy&~h_DTBQ--W|q
zqA^UaJMTz4tEggQ*_cQ_LA7j7bLyz8#cpGggy;YBVk!%oSdufoh5-FYAQ)v=d$Bi`G$^~
zm!O;En#M9uCykPzLZ5SHa%?hDHP5P;T4HN0L6J*r9DAvC1WWPOrd{*obfr3yJ?Kl3
z^_6dnXRoi4<$Tr!=4mhHg6ig~BatHR
zv%ZMJr-`8w_JyFEzUSQdp0HT>|9QQG?IXj$7Rbx4E)%HauDyY!tedHP
ztIbq;D)ckd-eirAHOG7icBH23*ApHA@nG*Jdh}~G?L5C^Xw^+nLWG+>hRi&(fnpY5
z?^hj4si6I{m1u^%i_yk$tco}28X8|}g5*tAEZYF37$f(+xT%XvO^`i^Ig}%cydrwF
zlpL!xdO->&@q|8MiJrAxt;z2CP*a+EvV`_2&
z<1=p{zjhmmYVkpx#RV=#zuy&7^2Trn=H$nT{OBVF*0z|QH!NxBF%gbqT!BEx
zKB!SsSUwSo1Zr?kMM%N)@hG=&m`vRQ6QK6=oIvnUI+|C)dGKM@jNwqG2Xi8;YCUHYRh?
zbl@DN-za)+0F9kw>Yv=ioL)01uFp7@AVEB0AH-nmB%j$RC_totFy4BKd;OPCMUMBb
zu3oUUK`|{AvkM+@KPZD4Tn$(VlQi&aWV*Uf@DO|FQjLOoVw&C@z~Um*h%Ka-C=n4H
z@(Lf&MDJXNS{3Hs@J)11(zo9tGp>wS^b9{Q1WN=Ktn>ZieRZS?k`gb7P4n?cl^7^*
zG5-oARAG#i<*z`J0ski%;QCLD-T$AbOHq<{KxIb4=QJRn@MGj=ns0WhZX+uX
z=oTjz`o-VviMt1mB0W1vA*7oq1ENz{<*-EU)U;r*ODfV!G-?hdnzhM@rRZ=|qaFTN
zX*t~$gc-)M7GS{#34R-n`B)eAPfebN46~61R?j^(Pg3TXR1PyQrO7Mf@xf<3VL0`4
zh(i?-SktJu8Oj?KIy4p@%5ZH;P&p5LB8
z^}7P)9h}vUP+1Hd3nNzNcbR`%1>dSZbWhiXe-CcB+s9e)_w<{bypZ(@cQT`P@ch=d
zSOPhExgI31MVFPsClEXe>$~qYQ+d}7(!BE*9y%AjQ47BMDt=#>`1ie)|ES{pFFdHa
zI)CK`f3x>)DtZnm!f5=e@g;3iK^jf!RU6hpjYu^V#q0uWLuJ-6={Ua3gDi9#*P7;-
z`rm*5)n{2QE{UZ01PVy@_9(amogzzOwYcVgp2>LsJ(}hKbX_!ayZ7=U{!p{BHussVj(W
z2z3$zu7h$KK<%}P0YBJ+)0unV*xD&6GusXqs=M=Cl&fP@Ttzfq?>H9TW#qDId+C7?
zhD;;HOxDJR4dc_xI7-b6N6nZ@bUWueDk<_9Rju2I*o(i)M0&~%C^
zc)a<25M<^NrsjAccydV2HJu_-1W>b;xrB~Mi@c7FrW-94$-GnKXvF7(
zA68!d!gkIo8(URS{(u{zRtrF}B$9@*)KH9POqOW-B$za4Sg-A&PM*on$>$o#L7pH~
z&YW8oJX3T!!@2r4Rr6ac0ZDbtB1b5yc$5}7oZSDvGF0FWTpZ#r7@GfM^MmC-p{9Qj
z_JmmlTxO(^(NHqBc$ECU$jQp^;)%xnyr$qvNTd`R@j$8JppDCGQAHQ7?fja9McCUZ^;``VW$1+G#=<;K{_OfH-
z_$fp~S3K`;jPNNZnkB@=DFQy3{6+Bq9nOf3~dr4q8zD_t{P4-^%<4kj!U
z0aj`=#@G*w?!4fpM?
z8Pwb15(Ka*TtDN-2aWK>*hh{R_C}*e*vSTkHdM(ETM!JrJ=1h?(_WL}2p#QXjrKZ_
z0k_yu^;~)#*r>sQP7d_4VBRvWJCzw#TxA{*hktwQI3ST{8{>3$KHJIgMGK6I!d}Q
zinmfq&RLRxX8P)_@@vVr0gPu7*)uU<%xS{|Eg;*w1}2=C&?7B
zSX?OLt-gZO+<4@tLeF+K0~*|xwMD__KxWgGfsUpj)KyeCM3J-f*uxe|xk;Dlqq%1<
zL(PaY@U(>Z#k!C!B45JlmE^~wHSH;r1c^kWTG9_VT~1LN6$a6Yg@kNF?&b0hs+5Dw=0j
zR(wcEYmdfgojx+Hzu89*C}4$I7^?^vYKhF(`>=MC)VeeFR}}?j#XeLnp8OhW9%9ND
zt6utD8DHnQj5@YJv+$USdN{8apQir2)Z{8_s!BABmG2O#pz5lSh|gf#CI8X4I|U4g
zhQwk=VEV+j+-KNxuIk96Bi%^(Sf9}A7o$zHJ5mV~)qP))QQY&^>9}z9z9)PWpw>8T
z7#NWNEtnUoUl{DP5(lmy<3;tpLJ3hG|;CGB`3**uH0tf9>;7w;Aq9SRVg1FDpI5y~rY#B|eCNpAXD
z9692@_%$t2^nu&4lU~(~_iVf|Cs|mXs-xKlY$-~FZB$!oDK#)JgHZCG)ySDURM=@(i
zCpd{Er89|l&)(&5>L6LuWY3yC6)`jPz(Po8pY=AYIBnx3y2Qx6*sT42mpR$zwx!!<
zHHCc~tbF^-bje?bo#~Q59Dmw_-VcliCn^FfI*EV)U1NkNA`6Cm=^%j`%M?1Zxa=1U
zn#DPNc32&XHHfUfmPx*J+3_GA&g-_pd#wO=Q^5bdhzmm)>s@yO0q|>ROV(hkhJWf@
zqWjI#+9Wx%C+!kp&kxX|XPS5m9CBC&3r>}SwdFd#YF_W78A*CN6mFC)qzOjM);Z&v
z#MjdXXMw63v*tbvY+$tDmuHNFunOlRM#qe|eV&|$98!xy{n)-=N?lrkr0_}U^sz|x
zs0y);(2Dooa;(9zHzRi=I{GSVcv!6jl%ck@)>JODfR?
z%aI)0HvbhzY9K7eYsntq#JvWzj$WCuoyGoPY7;LSPfZlFiWU)X?(-p}s4FXQcpIp00;%Jv;k0t@2vBu4i;rh-?{z}cHTLL9Rz
zT8r(1Ws*H~EyH+adP$cGv|7HkeS9p6eOEI*`idH3twkEJ*72|ey4JgISglGV0Vo@qe#)f-=|g%l$S&Onwl@mmdn|sjXXYaQ4MlfzjiK1*
zY&hWQyc9?G2}2s1fYnQ}LXpq{!&Kr97d?=a?_xXAU0SXrZE?T+=9os2*v9%Csph*M
zW{}m4+PIRmHEI;<=c5$PMrfg#MTs);4Tb_0**o}*cimSWRcxo(;G&&NV+-?W7v*%4ACG#t5J
zQP=$g-(mN*;B6s)d9JNkF0#Zz_WA>J;{=2a!IJsiqCV!YLjJ(wUJ`3b$>qcZ!HjDT
z2xm;fMSbtJ|3o~tc!jJ+U8a)vX@NcxU8y#u!Puq%R~{sps0msRFO2!GM4}786S7*
zxgNmf{q@|Sdnf6_he>gEGX7Hn)uih5nL&&t4`O{?V;;bdl1U~9RAnjNmt~1UPC3mh
zrR8ZtHzz1(yOYSK$OjKf;InJ+7mH$WfqI^OG3dhA+S!YmIgRv>2H78?<6A=~%E{ug^P+^b*+f=j32&Nv&Ypq?DcH&Busg^AUDE|p;
z8(tQxZs1+0gUX<5~Ah
zT0cGckI5%nM~d`uaMJ$o%2bt^##I0UdaQ2>-bpsP4P1Vk8r7EOSr+a!D*Z4shiKFL
z35Lvs^i;#;G{%ksUUo8(Nj2DY?u5->J8kqS_#{B`HqS(UkzR|K5&6XI_#FH4?$
znMXeTb$nmr1`|{n*#5H1T%vtU4-H)vrtAchme!ZG#@c+Hrf4uxx$;VU(Dr~N-ich4
zMKpdwot^bPY#kBILFgi?i3W_kV%vn2J+%R5x}TL8I?B~o#VXlmr?i=y`yJi-><;X*
zPCDrsU51x;mkr+t18lPs=6)r^gEh2$saaA!qv_<
zKQP13J}ptHaUjT_(*x+P}wfV-}57aU3rp#3AB&~e3%y}0ju#22u5@mUIT!GA{*
zd%-e2DTmr#$(P6^$&N0oCgR)F9IPR~!Q!x6YI*7dx6LR6n8tj(#1~!0rofeMtT#g*
zW%-p@V09>&o>iz0j66K^soJWg(o9#T(8Xx-P3?;J|t~nIDSGPq(?-B
zOoNnc5HZhsW(m6!J+yj~kjmjV6GKvhO>%^v5`O2I@4B$Z!~DgelYWdC4P>YfmI$TR
zq`atDEhIt5ua)PS;Yz1`FX@3Na6j^uBx_rNKTmgboWGwE6O5;iQiN6Q8>ZX%ApVJS
zTEf6oj=@?7klS(JaijG|(gO@dTgxB3#H)4&?+@VWkTc)dl;qK|uv;WRI*cG2`6PiF
z4+svy+Bfn&Fs57Jz6i!C(w$w@VWPAbRGak~oN>3vUg|Mmk0NpfURt0*DSJ_e*Gi8I
zqshW4F}L&aS8x~4*#{4vOc`gKW99cx*L^69fgPj#?++q9LidItd}<@&#E{ZGz7g|c
zFX$uKJ;Qv^NpN*e&EL;l@1br8j8oxO3e`g<911L_jr~Xb0)t$x$A~dFay9(}gt4&L
zyb=1<`|)_7(!^xJ14xLBGKXO3`R^_;F01
zG70TiF<5(=pRsJYj!^XjLl_vFJOQPhN#Pkr#G0-m#xG>q)GAHjE4WFhe7Zi83;gte
zdDv6+)qrgh3F0}$gPmtb9-Ff1m|xDD$6jX)Dcd5Ms-(@nKM_3)2+hfh6@Cs@-=%Z_
zIinf|ck6rN{EOadGmJ-rzvxZnAL)(mf108HL2v&m)%=a*?3CnX2ZfOQY?ha_11m@UzRqlkhrVbQ@0M(tSSTerx}IH@Dn2={w$iGqU#`v}PuV7I&A9JYNP%sqMn
z1bTq*Ok{V>SlVH8H*4X-lO?VzaDQzAaLvc1tTL+To)YOuj^V8mQ?)K-FT(s_!ds-O
zeb$rKRR-~g^+_aiGtH6kbJ)!K^ie;ipJ8e;>iy2}73i(1RY-~!(tk2zPj;pwB4k1a
zVa~7lF^EE`UH=#eb**88zBH%!WkO0S?_Zu0KpRtXN+XMsAwfT56IZI}&cs+R5N~p3
zlQH7o$(zsQQBPIRmD)i>TfdcgCSKbVVD;VCmO3l1VNbV&rWc9o>Pk>ex!)Nap%NtP
z&kKIFMm@k9-HeXj2$((SmG+a-dXvl7q(7n=8)cELHf!@Le+X)=++(}pKC*dcns?>G
zVa*fV{2FDIJNaK_jq)WE9MvxiTm6sI%YUn|S=oP0Z`vE#GMZa`4V5byxmv0@8@Zb~
zyBOJuTAG>Im^uIL@!ZrWJy6xL{%n;pEwY87Y^xYSfmmgRcgcEDfz4TJ#{;n|g>8(>
zv$(RLnp4oD1Mj>H@ar|0RCy}E{GwvuKOf1FS}O&z-Q)MmCVEK{p~b2xFj@lTn}#s4xg7h+r;n$TZDlT2AXAv
z7R^$J?R|*xL^>7HI}e>7{HszA#Y_e8=~8*3zy_J$ejuhByeI0I!w-&%MW7Q-FGMKU
z8qPm&IdU3w#^#`d%Vcn&q^w;EEr|w2F@ax^`R;a@p>l`U-T%~f&^`#zG}qdSV)A<0
z^*U=#=#o&gd{o+*s#j$xf+2y^t1Wj9_h}(DNi^aK#jI}z)v1rk-H)gocbgc`wB*?$
zfg~22r!^VEN+n>U8|3{Ebe#!9k|dF8lV*9c&9H~&g|$Ymc-2O^j9w$Q^I)ldd}5zv
zQkBFDS2TxDn`p}-{-`br?tUCgyfr0Wbf3QeATbp=9sN|e90U^eVOu0~VT$1A5))@C
zPcwzUn7bP^Gd~hLA@8EwiklMmlc^(;uPE%tLecC-iZ$_~jNJnZYn1A%r}=VE(-LG;
znh6Q+b;zKz_N7)0SH7t~u#)e>Pr194w7xp;V&CpmJw5j6zBO%yB
zjVf*iveYaWlrE~+p8YYym=-QmTd_F!`)ATishn6(oD}hTE2AqnVPF_os`ca^ET@@Z
zoo~4YJASOBn<;8#(#3G>n1E)&@JA^3LV7mK^kaJ$((~ASWup3G(%#8O%xFX8XSiN~
zUF0&gDyT`FzIjtA`<-+9RXEKbwu%RtcrG!#-aoN0aj)i
z(G|=#b_!z{o1}cIyw#n=j~Ac|NnR@<-CW$c%JFBFTi5JW0BX#4k2o2w{L0EglSN7E
zFUcmFVF&U6NBA7!t`Lut>faDk>pW>Lz9BSzsqWvnI<+L#wg=zw+aeL6=70S773#Rq
zG@fVM9=1ZibB`>L>hKz>rHG}`pX;dZD>I!_x~u>jsx3;0d$`Q%t7d<8^lkl8w0WZ3
z(HGiok6h^#G2EzIH}G*;!U8FW>@|C+wE+z{@e{wwWEkzUEiT0aDJo2JwZR{zcX$Bz
ze2pzE&vKCc6@vE*GIv1LZ=qSg~HR)Jf|ljt#^m2hZF4z|32*7{hd|u`C7{C
zjG>}`{SC3Dnc~5%D4yBa!V@}xSBtQ$ZWY^qs3)9jTuIXYMgPF5E0*&A0B(=JEntcVgC%ZO4UKHyuzuSblKNHWJ}OzVpeS
z?8|{P8FtkJ=~%YMf1h*@o-YsZkLVQU!43cY~nWEmBt#&Ar%7WClZK8
zSe-!M)B8((tj^wSIm3?e5oe&mQs6BAE#Y7K*^boU^Z#aITL%-H
zul5Gx*FKM}n~RnE*Ko3}nXrk8nTw0Ok-d?{|KMda<$n9cFHzkfb4wa&Dp0x>XjayP
zg-KZ^Ayey*gb`NecHls@$a-2|Z!Xe^@P`uYYo`Q*jKzDQGPFf^GDQ5rd(-X3n)&f|bD>?`-DktKL<0hWK!cPS>L^@|VH6##
zG*0#NtGfzpZpt+e{yL@K$|Lg*JfO%I+hp&kR;NxOJ+y2H49xZA7=^RKObPZi6
zL&R70!l_{PTFcxI#h+WsO^Y<`hE*z1vg9n7nG-6n0xBU8F8yDd}=?${Kl$qim3(S98@^W*vvSs{l
zU}!oUIXap-i#nT`er(?avm4Q4-snuM&-cwu#-M{K8n;l1gP$
z3sw?`ls1z%eb%&mNBvLuEci8}-Q`|kUw6;F0-pHb?+A)+BLSn7_@my}6u%J=Ub~(*
zU1n~wcfO|73IBZF;|Bhy$0FeO^>lmmZz?ZuZC8$p6<>B{Lsp-*mS05IVU00ergKWv
z(LIsLS=?(>QLLQQ?bdTpyO?iiEL`;>(XJw^lA*7FCd|$g@c3VRy#tUf-Lfs*_HNs@
zZQC|>+qT`k+qP}nwz1o`ZNC1_y*J{2=fCentcZ%LwW?M`<;L&dcdwa@4GT@LCkltq=Xfy+OasOLT!lXrqy`
zEW9YuDcfQtJ$oJ|Ln|b|q*_a|YPgCbBBfQ|5;-1(P3R`sK~3T`TtVV6yrtDbioJKI
zPDV1BAaj#O~V^ll>$#
zNC?nv_r5RiH^A2t<)qzcvns9Qd$_UU$`jN;KUSNqMCQiCFCi3A$*D#(v=FXCqz$SB
zyC8vjHyJhMy$5kCi}FBy0NdSCJa6{q(|*9I^zwX1NHX*dHOIDB8bsI3_{(*-kkQV@ng|lWd*nWx!(xQ1stGMcRDjH=YUQvY2^uCZuO%-0Jw5az*F1nW_|h
zR~z5DT4j&Z7527|#z9b}pmRW}p^|OrU(TWox^&Kn>YUn%%JlZJ^16vzy|O|GnZsf3
zSXEMjOhuYZlh*ikE0&zHt5va@6&GI{1&D+NPop@Tss&f!V4;}nqX@iOvdonoDa}J_
zE-u%qrrUpYVYSGU5NeXJr?#B#3dkObD8uk*U|u*zS;T2YgAk;_kdF0s4A6A*YGO4)#dKwYLQi+*i=C3N85d93
zAe#Lng7EX?@}-FPvIdp0y!`J@^1tg|IHwZ=C-i6LW7u!d>#==7<(?=6?caFCo;)AM
zwwV6XHIU7}%D3
z75#&7SiVq=f6k4N*gy{?o~K9`+fsId8Co*62ksPHLm=SB>G)@44I(Fbs1stfE==|e
z5WM)k7Hs~OwT#*$%<~0|BEb_6HV0F0=kYy;P
zdAZbN(@{*9FL}4bSi-&#J^2;N`G{J?KFD@i^8BEXQq3$Q#~shvw_cx5r%ZlgHz2&Y
z*cU<9UD1(G6qg=Yx{LRix``xh^Yi7@j|r7hm00t{(0ei78ZQbt`JV={$XlXvX91YH
zxbI<;-YQG@9xrY>Ar~yWklR>hQ-X6TUxD-S!;~b9lu;Tu@f59S=euifnkTO2C*G;S
z@TJZ5{$VG<^ThBbq_74=9q9r7DxC6VBngr@olJ}~W87-NEagn(;M*)7Oj2!(TG+}U
zsLu!TV4B7DH{}gtanAHawLkpH5_$jk$0~;0`rM1Hjkl;4D-KsjXTl<*z|E`_8Nlb6
zroi&vNu(socja8wZ}9J>;D}esqgs4BR?_u7ZyELz2k%GQjtG%Vx+yeS&QI*AK1Q~e
z;1-8)WjT?WqB>et(n%42u5UPI+!F^B7Hx#oW{i;??}{9#vpvk}lwvHPB$=-+pnIAL
zGBd3sTO%TRGFw?`Nh>DzU#VeO7C?`w!-QT4ZgBE!WsS1clJ&i=m$
zHn^;?BNx^_wESMCsSKfxi542WFvUJUh%GpT-JP-b+D|wh`H$h4?*AT6uKyK)=>%&^oOXr5Al10+ld
z9x<66pEk?hlV|$s!otJ~_Kz3DcB~XFzWq<@HMwvNFc2}VQuS$6g{U$+nN4G0`E
zua0)-H1D8k;mm6E{(!pNomCz*qxv$pI3NvG>(+Q4AcJvK#K8
zb9SOKS@GC!pN|JW#<}*37GFj>D1wi~_)k#-N5izNy0%(q7hMm?oL_Ju8jMFGA9bKb
zv$!gbC9lC0>Unx?+*3GF(6ZZH<(4j|5-Om02Y2z2IG_&xn+2Z`6;N1An(~^lQwwUQ
zOiKj)?fuj7EGlb8nv@wDs4us&o=Bt%l*TAhB{h=R+Pddpm83-ms{V0T&ofYt=D7dS=Kr=V{~wzR|1=j_+3Fh+3mcp0J6k#Z&$+yVt*OJ$s$BYK
zRx!5u|IH#%N;9@dV#r@$o(;Dy3GBon{2-)SK+R!>`0yL(nq~lFeelQy_)_BZt2i}m
z8rSXb0|MpaMQpG<_IaUCD@=+=`KtLmC}H1)-vV;8Y!fw&`K2B6oou$QOj%XL`Ye$dX*5~GV?
zjoCc8{4m*B_lFn=K@#mp@(*Vga>;sjA3Ds|(a_aGGbuFi)9-z>)&hY^h=PM>jvvAt
z$Q7Zfbr%lPeu2OFHW3uNyavs`ezAXnB`OuCGx+U1e%!gwF?S3T3XLaG+BzOfiLB-f
zLsTI!R2nT{#3)Z+EHpqiKXE$CK-~2S!*Tvgi)l{*o7SZiuHQf&N=jK$gt6|+nF)`Gm
z!Txq?dNfctW^}=z-436nDud8w974=Iuf~cqED93ykXqf1w8FZK9fiO>iyHhGH6`Xa
zy99CYP)x3@)FSqPdVt-Br1$H%x6;EwpuBzZ?#_D^RUI0KPMzf^_Q2rPhK)0jFB8Xm
zlV*;2seylEHqM|s4!E5>k-zx$17R0R2*LcwM(ea^%K>Rf92id$mc6SChy+Lhh?+zh
zvO6({dx7GOFjsuW1#TIks9C3Y1NS^K;IL#Bmt5WRAnNcc>QhlO{Vj2vmon)s*asQd
z33&IEDekAAXHibwHHW4Kjin6FB;UgbL))#+*%fRgjq!Uy)J$xt^A4P*
z=wpGU$DPMXW)DL%DW!nu39E+G5tKB@YM$r#?rOf~PwEaIWOZ?-rZteokPGZsqWYS4;B
z|0LjjIbp)2Q9#;HApIi0rAAv&MKYgXU3KhsoOYe|YT)zr{({<}EXL67@nFgE$g8n)
zlwsHK7H3m?1l)9j7MVEeKIFU&$Urel=||l_I+%2%vpEWGJ4%Ae=4~9emV-GN((dey
zu%{X&7)-JZ@$2L0Yqtni7;-H%fWs%8=
z=kT2S6oOA<-_q!hTShh=6tYB`my{cf^+Lx>yzS~3hAy^=8Fn4^M9*a;F$7-pPb`5WTTi>BH<(hQt<2d>L}bEO@qeR~R5CV6M#}U~hOs$t?sI
z7o&N-naKA!$TJ
z>&^XTo(>zGjv|b*XTI$ut5?7&&KtRH*Xif1`>gBEp7*Joo(B{{&6%EYr?;2euFLC6
zyxINGDCvA&Z9Ke6+p?I9Q!BMcUI`b0h}(?yqWH@VsM
zQOR!?^5j*fLK3_B=$34i3+r{u7IgD)M~W2q7y3L-307k;BupXtBuqlRxD3=-rhwa9
z?bS^@iS*Hnd^;p2cOp}nC~VDSN?;3$3z!yI^$)`1W?UAhtCjjqn>M&ph0;8EaiL{z
zu|C4KQm1Ko&6~iXk*x&^ph_a+*qDsevtmcT;T0k>1Tvc@2_|YU#phijBjGm~(FAS>
zlUlF>J!lV+cX^mbgNt|q+%c)}o#I2L8tL)BII4PpHABevx1oqq4Fk=enLf)lPJppehzt;iO9UQ2qK{ycJZ}25$Em8#QCj@IGeY)Ih;t1C_j5#Indn9>
z?q%Mr*&t<`FGYDnXUw!Q9F(&(vc=j2NyA|}`{O%(aBk4&ic|F*CyG^zcJTh7Jbkku
znj-MdZ0aPz3?=kXncCW=-<;dP;J9T1y-C;{aJj^)J(P2N6H-0wO?ZvS=U!GHKVCK<
z=aWv?u%5>H&8MwXa49`eLmGW<%;nt}*#2=)K*`axE(dLvH|fGa6F34#8tRY?cr_y0
ze3Ys0rp;JgADiP65s|!r+v;Bhhv}`Vm{n>M24Hc%zOJ&UhG2A;(vSJbsM4>fU{u2_
z-6VIhEcV`qxROML_k8tmxBr)-{
z0Nki4Ka!>@`U^UZ)eJ*+dVEKh%hU52puWKbEG44AD>zWsBPQobQCa)OTlz41wS`U5
zA(_e!#MIkQ_D?<^L@2G~TpSiQGc{2i*D?M}9=ed6<%52)rPN_&_Zz}kJyQ*xrss+n
z+*}R)Uzw_8MN}8>Nin$jkrHrz;R3n*HT*JD&M9fIRS?wRHq#A#i(f4q5+z;_5Ij)k
z55fi>(u^$A=GCiS!o_k6hWVWf;@9>(C^LB-^lw%JYn+7v`}UC04jw=#dbI?>PxGb<
z^hYM;a|^$Xv8HwRyEFBlC0EGDeVFD
zsI=F15ChE=aHP6tL~Ao9#WHh`H@ZcicgWiJi5Wg12JkaFg6%fLuw^#2^+FGSBYJC)
zcLQaBfXhJJeIf<*h>U>kVP9*cRCfKc<$@qO~wd*)<>-)SK6P
zJ@I^4#us1Hf$yt#&=?VaIkhDY^^W;!&OFd#L5S3wEK(42b#OVRSI3Yn=DLC>djb3m
zOx*FMX7ymI4;B56>=L7Cv?Opmx_j#kUAIX{b-S2c8Z$v=gOMvo?-ij^Qg7+-IsiMdRFM)v7G{O9O
zb{zD!lmDA*H)}70ZFQ4xTkLM$F*jknM@CK!9fA;1rEyA1T;kT|rRhl7MQ@3Z8K3<$
zthbXo^c6w1sy3usEhrD|+wtJ{DqW>!SzzMAYG&n5P_48!FI7^!mt^UsJ=Ii%VFz|f
zC`{_0n8zVxPB%8P&U9wpG3=awF3lq(pY)ZY+X0iPX>u?nXvOVKqHlZ!kPr!p?==9sB_~DS`Wz)
z-C{l?ZU7>v`xhem*b=STWhZXwe7a@WUN>CeYu(sj2^yMe+X__p(O0XKfx
z%AXEQxVFsfTzy)ozm#eCQhr*;4iF$jVCn@40VgXeH%1E
z29UQ3y$aVZ3TOp-E~*g`Gz^slv`Lf|RO$MFBa@P)tKRuI=cc?XxIqzmXgmw~OWv_3
z79M~sk*g{jtNxD4ShkFGO@d3`N{)-(L`+B$P3o{T)|L%BE`c71nj=koezdtBY4~a%t^5r3-m!3Kj%V`9dB?v%w?BxOI$&~!jUNWa
z@o8Q~I6n%f3*aDLLYK<|4FU2X@*``7jnlDRq5+VebLwb4vJVL_1XDYFTUc;$dW3relP0}p?81NZ&{!uRJU{&9)O%uEL4Mkts~
z&T=;)Kjl_c^Tc3YX*8y9Lb`*cpyU^wFHkn{Z--k1SA~|n0bO2_YwyEVv91paW(>>D
z5A?fn$`0!!94mEWTUFmE5+yocu&wZDj;aE3+jOFJ95*T%`pKWaqKNiaixt!T^#`@p
zHlA$6Fj^5&7!Hb19
zHyE9zQWe<12XmH)8IDIOtwPeM
zHRd&LKn-qMRQRtyy5LYzR9#*8JDBD2K-E^^INa=#S{XA+rW5XKtg>7Nn^Of&Vhir!
z+P>KycTUF|e~Hw_vAX%ap<+u9o9)jcAVaw~|4zkmS
zZa8>nl~i|D8zjQ^%<{;ZR6cbVD>%?nlBzUD&(9h}VOpBkVW!AuVW!MGuz;OfTWE_|
z{yi!0mE#74$DH%4$iv357s-5PS(g3aXJUS?=I-+Jz4Y{Czu2{VMepL1!wV0l8b0k)
zSH~&|HJ~YYm{WKY&gKO*WNzB=l|JE3C?T`VIh$Fi$wHFx68QWYRy%ziF%z4Zc<{>B
zjkGSyv*i{+F*O@tKQ!EDM%7xw!z{Yx)~Woo$kr{Z7+t7ve;X$MoE{R-LVe22TZY;%
zOIFYRqSw}4;Mcno^z?O*G8Q`&wbgNV%>E*DX{fnqK*lP#K0dvcU3endLW%GugLOH<
z>Y{oG#ECe$UPvO#$t@?@GA5JFE*6oY@?+$jRxnx(BiZ8q{AuRkwymR+;{*D6-bh*)
z-5@PC8lo`?K**Ec9*n$U>OJRjK0H$J@vnMoQZa4ti
zMegzJ2oft=1Y+aEG$4JE9{t_I{tH*SwKVixk$IyL|hvQq*qu&_4C6X
zp>36)v+qAXl|OfXL8koN-RrhNjjA36)N;pjmTkOO>jg}c>35j<2gH)fb7QYv#8VV2-AXJ1-O{Vpi$uIz3lMp3dl`?Wwpp>|6_$}|ROmbQ-
z+O3VID2pdMNR%dc(_#%+-P-%bNIb5Irk&d>rOY(_mq8%P;dkWuH0mR4vhl=r?rV5g
z%=n2Yz2%@f5#I6!(KxF>D%1-3IyJU|VW-!(l$}cWBQtobb>#9D+>HlD>@kp+qgiCj
zU_Y+2nP+9m^gw~vIRygs?R~aXBZ*Vk8cFZj_&b8(pTaY{Y}cTT
z*fRuKeL3=89rk16#2TNQ%KL}Ryx)%5M0MHy=A(uL9M*f_;^wBL-FO~J+@|(7I)GQF
zGxu8y$fzRDE)xoI0MCR3S^FKd3Mzir$&35HZu)9V$~5*Kk^r{%vt!7ISD#%fswRS1
z7x8ugQ&u(usOPXbN5Z5URhEFc|NLc;g}f4JzVjlUxu&$T#yH-Omy4s=$~b=B<)v}=
z;R7RHY}oe#TExRVjM2_)jF*Q3%G{)3ZZqgSTa^}wnjk_InITrx)tW>
zN_A5pLZ9CogVv`5^1_9Jm_n4I&Od-1kC6YSPp-Oxyt0!D
zIplg&zC_?4NKvoQui_?BUY3EYOP5n0W0#hYf21a%4Fg1xeEs;w-CE2d_X6pd9A`2e
zuiIRY)}Lqe0J(eXdpq{`UG}5w@h=I2qwDlnybY&n3-F)3(mWK*z~Y1=sqQ352UCF4
zQlI=T^y5Lp>gG~>1T94`()}Z4=w<|*zIWTL=+#(!PT$k6nPOoI-RVk#s?iWB=$tTc
z;v`#9_oLoCy7W1j8Mn^hfr?}kDKcERb3jxH4>hafqve(?N%m6{o48;*Aj`VQb5)Ul
zHK-31_Fm*+OH8EXSzh8{$7fljqN=ahTv<75(Rp-SR$Zz#EMGFOcXfT5%J^HHx8x@r
zP2)nIWHes~>%OVy%4>O3(0{X?N*ukyQv5>kKb>M|32-D&p%1(V8j7s?3w|Lp63nOV
z937ts^a~AioVI92W$?353}~XMK~{A}5JkKH5b=n9Ciq@IDBAB;Z!IUAV+ciiDvH*j
zMD^3Dk+a${QM5$azio{#f^OHOx>LnJ+5kbRm4^N`5ii4(4>XD|b?3s1jrWv1Z}MFy
zT9v+!?Ds9SiLUpcRnr?JG+C=^SKkC=BwXt~F8Tyir)=)czcAl$Z)2R5pR!H;e=OVl
z8*}D=$~ONscK(|=^G~^sSitaqkw<2U?vov$hY7)fa=I8~62|7IuK10w(qZq9BnSjK
zt$S9yI^QU{77(-&cteiu27n8-8*tNC&-dMPS#upD2hi$Q=J$O0#Os?xwTN{WtSzZC
zp0+5nsTrDO-C3RykP7Y)6z8U{uiQ@973Pg|STBrbPO4R4VU>jA3ZJD%OK)mD`u%Bq
zjUA|-$B9L(11X}nY*naJ%@8ESe`WsFWU8vR=
z2;2}9@)$?_zbc_riw26%Kg!e8Kd<=z-OEDxpIr0*^LqcyFQ+uzy_6rD_)MF*+Au)L
zK+sV!gc8RX!}1A93BeHY86igj>{s@tCS@2Inb@Wg|3Ir$G(TxPHZ`*>y-_zsskEEv
zlcqu`YL%;Yn6XuOyEIg6vQ;HLymz>grb&Gw(*q#A5?6USh=@|D2=%(`I*cmsk7f^9^}}P?
z?OW5EW$5ivagZURMyiQ!)dSTd0?Cq6Pu{r&OKRfiuu+&nj(M|bhppFk4ze_}sSz1;);PvKNiaE=q^G|5w^Vy2SN
zBs0Xts91C^d0dq<=JmXesd8D;1K5UvF9?WTYl6d%lJqXxN`Pj}5LxPgSRE$%)Se9Nn;^;MLmXCiH$)23AiNRlj3
zB5S`@U11=y{xj(rqgS3zSUD^dhUILAwb|IZt>UN#gv=Rm63ig{MK*6HQPQQC{?1ODO*flB7}Q(AO3hFI}(g&O+0tS_v*
zssss=fjAF6c7M%h{bJFcbm>-<=R>Xa4X{qGb3|a97zk+R8pO+p(k2^QM<;%(sz0y~
zRB?%#!Lct8vXEtAzqvF2#xo$NsieLB9TCSs^E_?X{@2BD7<@uv#vvJzQhJD^v3!dT
zl|$vIA|g+p5nMz|Au5{UAyp|$2kfI)S~hhN0%yOnr(#(o-&bKg$Y+VeF{*sx3Du~N
znZWwrE{QHx{GA?2J*uLTQ+AKA)Nbt+N2AXvftlF`pev3SOJ$4`MSDf=HiGkA5i0UO
zd~$T7PLbVXMt2^U57wmD5}@X1U>&QO#B&jZ0J18_+exP+Z@5Me9xd0Jbq&L^e7(>X
zNNZ(5fx4(0i?cEE=!j+2!b@EfJXIo&j};GwfS*019h#N=Yt|*|0J4`!D5
zN_q7;3^d-)FNmK&7&H^rwGK+yh}q{Hpt?|PFC?Fm#mlG5xknmlrQ>IgB05c3KF~=a
zh6K*nAvP~CiOXlXY$wlxYQ8_)WN;>NeiQS5Mb-&Nuox?GER-8$-`li(QhmzUy}Keq
zW@+_RPM`C|bx|r{2{VLpv4kQKehI>QOprT%3zknCxVb_F`5u!3W#trOn>06Z6D*XH
z=M)M2!jWK4RGLfuttE%E2P@F6hVZljI&jmjn43^
zPJ~{D)br75_H1XB8(ej-Emk3-$#Qk8x9>hEB<9vjxJQ=EG&)&*v=3TD&pvVnxeR-)
z?Lb+YlOky39f%jYERz8;%h7@zQH?O%8>!r^nUZ(>IPqq+lbCHA8Ax24#IZ@dwzGe_
zNr{+ocSoD-L2*Xdg%@t^OiJbgq#@1W&4(>T_SLJKpM5HrJSQaRRfbG&uyI9+T~>My
zyWR{C12~~%bhg$$vJk%xRx<*^v~v)B^3%hV33i~-tUvA5Sfb|5i=rmc9n>)2!GqKa
z^P&<_F>DtK$|77CJ5xuKX-Q%!OtxP3n%EsDQrn82M%6F*?l55XtzSVcMPQG0ZuQjl
zmq*Ic&aackwk$S6PqbQ!TT;VJDSX~x&h0RoXfrD8&a{@qUZfVn6$ilU9V(GVzCpk^
zP$Zf;Ui%dnVGK2;ueF6kZ
zFhW{mY7j^Tftei%owFtP`AO&4M?tOT(
z;Htw$hS6rDA9#f<0l{2DA~U)NOfScqg!^m^q#5Caibizsnh)JfGIIAiSiC=S%J|_X-AWeS|ich7A5v3!>zaS0qG@+}6
zF+61ADkXR}zFbZ1mX?PdOp=@C9DI^|;2Tz^0qedK3>_4z?WYMY85qL(rt=Zq14q`G
zmX)L~hGa0K_F1zeK5O`YjYkt&x-#C=rX%}-v%xC}Z95zssU#Mk{YR8Je
z@U4Wha=tl!xo6aPg=VsfWT-Uw*s!bATd!Jrcam6JES#?b>09?3j3HtW9zjdZo{@vm
z;Qsw!K~TU*LK!uvRJbS;OkNH2Wt%Y^x3I4&v!zodO!!r6#`%hm7yl~tBXG|sE%(t=
zztYj^vC$ivB^+7S$l7s@do8-L_omu&g;hi4Q7^#p%DB);DAqKLC_yf{M--fbVCW4Q
zpLSAJpyR=Jw|FpZ7!OY9&`o&H;FE5C-006%H7z?V^+c?EUl19l4m+%pxM%W-d$e~-
zt(|&Ex@CFK^ihfbnmM|@OUuO+x=YOaa6Up`MZSv=z+
zj&v;Xfs>|(JoZyyf*n#2H&qEvkEBqz1th01TIY?cy1siJEZd%upf04|88q_e^UcqIJI$qO^tX{0Q=;ytn*d0;d>W
zpbMg2hvsXQ_P18QOkwPq?4dM+V|(uRBPZ<<$bpw08v0vS$9$VUpbm=Fv(IMqMe~ij
zM>0rOq>iZMoC}d%y?jB;97(AMLyv&6Zzi(5LIvB?<#Ywf0)mZ_~Rdangdl
z&@8jcCHuwoEo63_;{rqY2HFx=n@YZylX9a}
zl&P9Yv{)Lgc|b3Q1o2l|SANshLidoYfmF5?I`bsF`E$9kGP};}K?$qva#L^~CH`
z!TFGfb4WF(Bq_ENC#V_OREgx>tR!Qa(Jg2?b%7g;M5AE-&>&(JHfZkcmN2s4eJeN!nCrcl9Way`gTk=o|nGo|BD1pGHLvB0ih$H-WM^@K##RBrgEQ`4$CSNzg
z8QjInTy|bpvXE2PqeM9*$mGvZ!Ps7Fn?$@*V_0OIlsGq$7xq#m0A&oC)8WX5OB{I{&
z&m4D92ULj=J&5P>4A>lRn(KPS@|aiq-&TfHnOC`uYpkgbZ!za!sgrKX&HmC&DR$Qw
znLUwmqe#(ab!;OBsne)NG--Cm>qV#<+25uf(vCyt?AGIMoJse#4t}n3bFn42(girok)X
zsLlF0m3f3uPV@^VjN3J
zs7vW$dREOUH=t;vnxK-_6qp*ejG&zM*m*>v9wu&xniWe@+eJ-67VZtoVET-b0X5{6
zr(c*Y=7z@KB`=B#zMR8)M_(&sn@t?LtNkyD`lrk0nJapT+`Ued`PVEyOY{v7f2Alh
zxP{mY>C3kmqt~@Sx9=weAH3PUD&9e;-4Z?DM%u2JrA~7?nOo3Fg!@?ilHRb~Q9Vh0
zS~k)vttP$Xy9A>{?$-j{oKIM^!~^qOk9nFfO9U;uX<{Z}MGPU&T0}pPw4d7EHF*^c
z(1Qo888T#p5hW(|Q-(yg#r6vVzhg0gpd>56bb9oH0wu}%3M)p2fxFLEy>QG4R_-h8
zU+Al?!eBv?3%sHzLA?4>j0E@%7$S|RYf_S$ylY+
z4n%*ot_mG#p83HvVERPUjJRH!Ay-9T%yQe2biJr+b%|?XeE(`??bZyWEqp{h5`F<$
z|26&q>X&o$0crC>TI-zNN~}*w7-kFnefLs
z2fQs{{%-wM-9ryBgJ*Iuv&{5yuKy+Eoc^si>??Jju|gyAn_Uf`ajXB1%g`EBtwiQ1
zx^awk%lc*V?-yf2mx&<2oHk?3d{TaxpMu&Sc>d+t2h>+*DNg;iw%P+Pbq56MHt1{8
zuC!j;1YlpBL2hXi-rks7|L=db0Mz7?nWiEF08stMZRP$Sn6!kAqm#as74d%`|J5u1
zZ`hY{-1iNNl
z1=2bj@r1^~3~TeQTAAId%fY2ha|!FRU6VMpiAkkk@VViqVwhBxz8SBI0v70InyyD6
z3Bn|Jj3nVomoatTh{xa7jx;yvi_UnW_#l*M<|9E)rOc4j#iVycL>cKHTtp3#k-nKL
z+7?|mS#aSINetxl?nE8)%Zyk>!C1k`<{`huyPwZD2`YbK4!99|Okznl56^r1}88nU&cpyn*~f
zRP2FGaX0@#FpvKuii!WfqnQ6~#DBA2l_uoxjK6W&?wmdns)%IKg2?m;9KE4d3H+J4
z{P-@21_oU4WQ76zv4`7rf2c8VBqkLlTWX8sn;VP7*r9$|Zvr<123Vyh&st-dNnOt)
zxtL4AjW-w3bde9fPrdt&)f0toUJ2&UdD?Duy5Ap7dEF=0V82i93p+Kxkri{*^!QAa
z`)V#?MO?Egc}EaN?0rV`N9>*U}noU~6E-WouZiR;Mgh
z;i}OVBurvrDpRj7!i%ICbMj)VT&(w5JB7dEWs8$MSfbZaa1D^jw$rlh41JSI!*+g5
zc`HjldKt~dEdKiq-t`OW#SHiFi#h4kU3|pR`S;CF5SvpIp|Cl8#>|qEO
zL6o_yj`uN0$wSqXQfj)_qWIKrnS3$j-u8y`GrF8k5xy*m3E_xC>4xG+3@28lsi2dl
zG->G?bNPxG)$u+RlKOK*4722EnDvKFTfCP}MVn#i1AP7T_HVVXeMTs4JO
zpT_!OPG@)cEQ+es9a7Q~8ZJxuwg`RN6PqI_ZGrR{=g#vc28nWQy+I8dcb5dFR^-u;
z&&P%sTVJJ;F`R;9s*$hDbF31St>mkHWdp=P*}5fF!x?lQhPw$TMi}e=#xDm^PWJok
zBklIX+F!cN8)z!@No~Er@9ywmEwj?-&7I}xh?Aw0SPtK(3EQ+5LHqwwu+}k1p;#vH
zrvh`dw3QgL-4@kIQ!Av--?{@#~s8|+dQ;(;Mo#ndpY6spn{3TJBv8{Ee0%vgX2)N
zCCV1=Y(p9TH+hpYR^mG9QF6nF>tHb9wDPpXRlL7F+QvVV*IK(W=+D|wiR-*I;elS7
zY`O=x^{a5b-2CDtug6c%+y!Jb>;Y$1|5k+KbP-$ndnLz+PK~0IJ6_kenCmP!NG!nT
z0oX@l4sD#DBU$@kjnc{sh4baeOf!mqY{x0?+@X-P%tFTkGt+fK8Xnl}SW!g#bX7&^
z+2;eo?q}&im*rirs}E*eubvzp8ZZ##(eDL0O^$sfaX!0;rmj^d#vG<0v5$vbadqkM
z;c@S>jXq)Rz%lvuo_XtEk0U!0-X%0LG%_Oo&y;sC!y!Vzbv!1e%gjo7+E(!P5CXQg
zglw~&%zv|GAITU4^EUXYL*ba5L|+fG{n2f#<$P`;XXQzw!rFG>1xIQtjYXPCx$0Tg
z_y1H9*k8*NMu;cG(T9I5k|_z+!6-KvLctWLG?awCF`Wto6>5{_B*kX_J!#TlRfW|Q
zTxT2;H#0}=YR;55U1N;$dTp5H%;k}GCmbbyfA00QK5!SnK;wWT_=y7G3YX(F_2ej
zekKG-;-FFYlnsInfBS-ue-l(=JyzlnCV;dv+bFa!pd>$1xZyr37BgGGzr|0+^O~0j
z15^}t&e-E6dU|#)QNVmuka5beLq1^$=n5hx6Mg@fLV!rjf(f07zjUyE!{MRr^$O81
z9c&-SdtEZ{pn(T}h6ZnUS7wPMBn?d!5HMe!BHRBbb05=@24O?2h_`+1
zSkky=Y6p<;hK&MFs_UV3Pi4-ZFlQ5qOdAaJ4>=1O04Q<~*!bCF?FPS~o{er4?b
z@BAktYAQF=_~SF#TF%vAsN~HdgBetV+7Sn}tl<@KS7SOg0f&fC(;da%oL1YWSL+*m
zGM#5P_te#*^#`lcd2E#Bzrd<*Ozyihcs6GM{UIN@;iOnS-MRs~qr?3IfIIow<-ibm
z1axfeXk3WdOtrvL9~RrkL@RPE27Wm{vO5xg=Y{Si6xRMyB}nHWVL(7VUs(tiyCf+=eFX
z^v*e{k1Tj6MkZdZ0LiaYY^zFpCUo+Dxx=bBlNeU*IS#VeeOAzI)Vt^$zh$j^EZMHM
z**h+Kz~xZ6N@mz-#ETTbxO`K|Nr-N;@=2jQ#7ZgkFx(W;GWygjB|Jx@jU+qS`t!IrL_@Mh#X_TZx%@
z^4p_*L+-*ol_Bw(5gpCY^}j0qLkVl4eKqJivQEuSwK~_wQU=a?(Pr}B&EB%
zySux)K|s1&x?55}O1is2>5>k~O$h(?yyyFj*W>Z~9|mI&_Fz2Mnsd!nbFSyU4NmP*
zk_r34gxePNOJ$h6cykvyCw$qW0>}3|r&9U*AFcQWu@^Z90;YM#zVCO^+rx
zNH@pXoqevqr|SqP@$wvXr8J@&d_JP>=uXmMSW8G@sN0shx}NXhJ^U;k3^P3*Y9*{X
zT_){Q>`WUL%w79gi?=u4Dq=QB^rnC>Qexc!1mCKET58qi_4>ylhJterN@VVP&{9R}
zf`VGjgzL=<92XlYXsi4V{!C1%tpasaKFas6LJV)K-=vfm;P_v(pq!FX4Y?&YsVKhO
zR%%faHzRDbQ!M3E;64T2WnRzcuczPxKYjJ4E?oK+r6|}!&xa}zY4)CB2A?|sZ9Z0a
z|7}5bo3I!eu5axh5J}j*49lzaa_Zc8rw3g>pdb(cSDK@($H8DyJ~4-_*`cwZ$s?
ze5h6-?o%Yb`5-tXa|0?FF6Y2tk6?PhbB~VSfa6cTW01)6;9^4dE+jka44m<(+qOx|
zS7+%A4{cV1vYAlL_6DE@7TAVxXLfPEJy)0APHnPc=nL6sYxCkc(#=FY#J=VU)@bgA
z0_~_L;7&Dz1PtGWxfn&<4}Ma94p>_udw=f*7k4kv58VQ0lC!J^kehlmGtWV4Mi6UiYHz1L*lE`k@;g5_yK$-=
zZtu<-NFGqxlm4JpB#T7g%Ex-iNmQO!&y7g$cHfwbO|=&7md}4l4Mn9|n24rEQ^>Ux
zYO+gTedMAD(2~_1Q6k*FOpy38A*yn7gLcbXj?+s+U;2tl$BG4xn$@hHmfNzSfuA*V
zDR8OI{FbT?yi6r34Q}@hSTAGKo2ggB19-#DmV2x|Zadz2|rHCQV8f=qYq3S-XQKr)V!L{fbjC(JB{i1oZ
ziF#JsGKmxT>@0|5a3}*}b2#dWUIr!i`8n>4;r7E*)&qvB!SvEbZkC%_T$i>HF_iTK
znSw(apn9nYdcK)KaXd!E__$?es}T}>(H*ztldjGo3~FxJOQHIwDEbA;V7L2u0y+iR
zI
z`Ta|+1SVzj1fro-ACvhOxw!`lkeVnt+5zUv+2Q>l6W3DEHS!?GkLeUc=jF=*DYi;4
zgAmXvqwtL98S&@oBP*(OL2;6Q!{jJ!x!SIzc(UKP=n25KVnzea3MJKb=3u8Cm>iLlc
zo>?@$-95+WQf~)EAZt_5R=Kx&-+eesXf5(h%iWVsgV-k<5sR4Bt?SzA!_Si!Vs17{
z{6tvfF)5Sptk|88Zta~Yi^wNgFB3D>72<4rA$j}O^elvaJgTjo4ShF~YmiNpHeGbr
zyKXGp)-!&Ibd!z^zbI+4QbF?)fGbwcwDyLFza9Z}=ghoEC1>_-5DRf*_-4`0`D_3%
z-j$9^NUELnMfu|?&hgFGHu3n@;Oi!chfyGFC1tj
zysM2L<;pVB&eZILeivP-DG6^E!_0P@Pv$*0)yMcNP8S
ztipdgy#t~iDVyOeruzZb?;xzt0NZ53utk9^3ZvN}(iFQco`XI5+!2~Bt*g7s$UI9V
zqTk}E=N|5KTZK~u!6+3ngR++0rc2UcL~b2^1ySOpH^5EkBa;19dk^IoLT_D(^eYV?
zh)u!~KjQmm97L8GO!T6q$6zM-+4)P@I(QCal||#8B$YWzh+EnD6~{;lGD;KM(2Z~x
zbfm^>#(c>3<`9QS(Mb$0_NoT37Om8`p*ft5u4+)-eY&scXqIdG8ph(=r%k3w~PVLOXd
zvY%SJgzTUS)}20bSmIE#Ku2ArE#^+hFkz~5s)Jq}y~;DcyBxahE*PlD`+}A(u^rn<&8zczVDn%^A5dk-Vy_mr0qL*uM
z+kH(G>dhnCDc>o`r?(AIs+^*rfe)ECTkV3CYD3Q#19fXQhe<>BD4P`WFJ{4fglrGp
zMC#o(hLNzR_6BG%EOWFS0kBYlhLR^aX`ly0}L;y&ATq9Kgir+g(JSTR7eC^Kd70rtk@Qwh@u3M8?jc
zvgkQ+ER2q@6iY?Es?2yUOPXy52HHmmw09OlCy8i1JSX$cFQ?Kz?WxLaD*;xXXdOZ=
zBkjariS2=U=4{ztOD4WdLby%7@-N=%81G7r_onmAC}*~wh&dH`ElcXAaT1YCg!*3c
zydPyIQxoLY1}B)t!AYV-sVm|=v@yqXQI~?W4Le?d1`+uZEGOQ|ee*VGf
zrT|&74wW?}lFB{`V02N9RseY6=RHwR+vczuOFPU6KW$IutXl`cwNkIGa12qG
zrJ%bP3TNk7J?}yS3x6XEWxoN1EKl;n-Jr)OR82@8A-lLcqJ0m!DhivFnJu)P!CIZozRj3Dupfu>UuxP6njtRWN0x(t)#GPjJ(W*QX;@KZebajIc;dm
zCW~hL0jRsrD=aVq-P|3Oy{?-lW2lzd!ihrjVFr)oLbOS5oQOiE*S-!;?Lbx&bB@wB
zIBCNkoH#5Y8I#5PlHx>EpLUEIfBnTV;pU3R%nfkZ
z!YFhE-!>M@7lKEDX})s?nHWmd;*DDNM6GEm7PaY{ePtQ7vU*E6^Yo7t_xmKXg?pIw
zLetbL($kGYR?TwDFJ{6?y@??DP->A;k*WI-u5h`r_Fj=a1?c8CaYv_fx+w3Y&sz)#
z5l!Eerg8T>?FtY$ym)%@xf}a@V)bx@rCghzp-=;#(K|s@NOO*IZA)NzB23n8Oyp`N
z6Y_)!pjq5GpOl;|9mspLVAjuk4Swf>dB>Z+oWGfksTiJHt6LL8{)`TN&}5mlo&S@f
zn?k$j;4E88b8ms}U06xznINvR%znonws$*X0nXu~KR;D&0=;
zq1MxLBj~1VFmZ3_rpJ&0B|edG0LL4z$TA%JtOE-~IHfCXompV+wy
z8-&6rt-RaR;6BG2HZ5IoYkQ!W1K80!*5H1C5|T&@US7!VmLWU9nG%2IR0sf%g(q;p
zir%R2#OCiM-FRbfu?u|_l)-Q7I{}F_K#B)nXF9wXSLm-9xO`&}clEL58GaMK6`1Uo
zQKob~3zs=o{h-kD;27bhfCkdw{8=X?mD$rB(iIfJLV2z}Inma$btemM>{3VY_dH`c
zRmH*W_;0{4Bi*0y!=kq3gCg}!KzsqQv(?<&2%Y|52_E_JZZE7axCF6;pWKz-h9;(1
zFEg|lBDp{TkLtU9pc8X{8!)$h;lT}wYiX`cFvH{sCC$IJ1nrkGsX1R-c54t
zLc9jBHVaK(PZqQAK)*w|rQxaCi@4yDsR;BKp_0+QMY4^V@oQdty=y?g5jigp7$EqZ
zjDUR~x@7qfAlguTFi<0JZx{E(?05$3ZrE!(`+7JwC(6-O)0zPfL-;9#k~GMZLtGy?nM#)>2+T`kNj
ze-Cd%!Vd{3rx0cOIo+1L-plN7F!@)*0?vWum?{xsvwILKF<=UycOWzqNrt^1DAHo{
z&>l4+Ab^}}aY{#leq4;cq6#<-V$Ho7UKVZ81@Wh+CFOY)SxBEZUOMd5^n&4mJBI5y
zhiL&%RP$EK=dU%dsx>v_%dKWSAnH{~OU>To6_twC8@+RTFwOV
zjN#5sZh{G`WWFrn$+vV8xa_EdxGegTh$iG5fdf8|IkR2eF_u{^F!2%tv7EYty{ytY
zfTzxF4)ngPoP_WTG|Fer08u&Q$%>o}_7yWw_VUke{^I-nDIPLL`#{~ep5)0hW*8ez
z$=vvIc7ys0bTt^Z4cC$pSAr8jP+)*}S0n5;J4~41b{%cIM*fv_$1_a{7~CzEGF*%a
zmo!~DyV(mH=a!>N6aTXY|l>8fd_G+w#(nF|q5jcLBA
z13?#dl>PPCA}RNzqD6oVO(@OKym{I-Pa5JmLRwqW$FBiUBnL+P2)@~J(ec|s_sm!R2@$OKicGYN*2GqU(J&T
z{Lqn)*=vxuAX1Gv0Dk!C`pCTtlDrGq_gKcHI?^jian>rS^UL?G0{-ilaNK#DTyw56
z{Mo5FbQ?Hew~5Kllovle5o!-n7?EA%~9
z%jQnBip8H@%a9KGo;gZW59-6s%P>_Y62@fk&z9tt_3vec<8wZNl}y-DPVJOG|Iin_
z626Fx(_8z21@R?Y6h3=m$wyZ(m0~u^gGm$C_>_E9bIWd}w}}Fi6`vO0&SEgSdVWB!
z70oGSTwI5)%Dq)n3w0Upp_=|g;_;3OZw=}>WJUsdX*M=A4EsAwYD>0ZPrKc^Y`%(P
zR4QJgyJNu4aNup&3279U6_
zdbsfLmw#jb+-(ai0SJf=$M4ESh--^XS307Zgwt`pJ8{}aNm%u@LRcdGx
zw~H)F7#NIpX{7#kW5V(1H5
zz5AdL#5;!Xs~elu2h{fX{pR6_V=3+&^ruJ{iTx$`s^O_)RYD@?{ol+}(o43PDCFcy
z>6@z&ig(9lnQ&Je#^YG*qG0nV5izc-nDi1Oya!vptC5L&xq!LbWas62!Jk9@Hgg$u
zcf|NzytpAfC_?Eo)ZG&ywyD+)KyrtAk@F|5=o#Mda4t2W8yW1la)U@5zE9jn2t8L(
zX81%5B2%>F4iIQQ*!=|^;t?PSN?@8gFwrSJ@S3$#y8xt&xUbuD-u=7}9#eLWR72-qTT@xu+BTcA6}iClYMq3D|3PS&w~_olnHK
zbbUG}X3XIIUV2VpcbYSqR^lWK`E;G4pb|N_JYdhO-P9g;3Pq
zx#XGZHE!5Xc?m~}&3$AbIXJZLI=xQV><&VT5CXbQ&*Kz10ue(bo$2A61QOcN*>`p;EOKRNXLPtn*{8w3F-Cleb(>;Dq;Q;C(4
zd?J7xq=(1C&}V+H(IjuWE!QWIPhSF^7YZk!fUfOIo+QzqwU^5k7P>3Y8U%-;?GA!O
zHYcntF5ohIP^By2K2uO|W-gA~czK@O*61M(U{K*rXX`j+=FR!L5*bC
z8%ZNoC}V;XL!Kpb>sP)JkSj_sf;rwMx2$<+g%bK77T7~8tSw-VD@GV=JA)2g5Hs@&
zN(X^2sMAj;J;5fpbBvQ$s%Wr@mKo`t|+60qbQv%_fRc(1N8*2fDS
zc~Y)?i3pyo`Y`?2GK=TmHMB1Sk?@)-KhzR}Oj=qWo(Ut-uUx}_lC%xNatZzBfmEBJ
zSB2ILfPtS-VxP5RivoeD?|F1}MKFC}S2DXwe+>&i*)@^(pNc<0Ylm@t;ENoizkQkG
z#jnpbKyf#qNVcsT*VPwT{GWW9AfDFmg(z^eN2;&JR3~wRYIg?8~`b
z6w+Q}ETeZ#j>1Z?z5425VK$AnXI=J;)o?YW1AC@*n=7rc0xy8rmLo~Jcb!bgn3ceG
zv1@S2g~rpP*}ia;hD~CRV%Kn2XA_Ux$o_4-22CZ*sM5r!eGy6Peeyw==5WHgAUBr!
zfvRYibkq^Pj~pB0`BIi)Xx#xu3H)+%OM`sS+HY@3+2tFUh{#~*CgyA#2A6>lqfn
z6S5O{6{Wk3D3`MS+HG^VfwulGBaN;h`#huNIg<4%zjQE;0edb^GBt_26eM9Eg~2<=
z%x&8wNd;sz2J(b`T`Vn+b%GZu!pg_&@u44I_b|jc_M^Ast*GX%
z~cER`C{E`DzN*%y4r>@ti4A$Le2~6EEK|BE&%nFopIQQ
zN!-D9pX<=ija}?3M}Wur)SnR4!Q^=N{TZI>K-5OX+PuZ@ecEdP)O|3
z;Z49IgbEtgSJg(*(Aa^$Aoi=5ZV6^_E4HzP)mn?bbRzqSk-Q@}P!
zU^@l7uS{R0FQ1#*uh%#!jP+VDBI7|deK+xz-o;cMwsFQa_N6oU`m|HL^uTLD=QXI?
zqFiDND9*>fT!W9Zuh{5;R})jH-(6Au;dQ~kD`bIM)20??E{+DjC_(m7K9a=~L+3%m
zmtNX7LSUw(wb78YdD4gQYKDwb0w6BK=Xyc%RRPAvWSvJs>w0h2R385!%w)PxhWr&M01bMie
zx>a1ez2u_4;Q$qR#^a%(z`bD;W}PcbW;gZp$;XJ(jj16;20aY3xp5(V_)^EWM`}Gr
zK#ADYB0DVWY&9JP_oH)FDL~K(Y0HNT%jo5+7MAC6`q*B*BqP)IfOA
zSs1}p4ht#5?g87B?XYTl`HxLvWh($kg4e|Fz2Zvohr;hXR?n)(=s&V%ugp%$J_YTVFooJk<#&j9b704}aM+b!QM*
zY2B{6NUDF@2GpzM?B-{6Ghg#rk|qw*Qr=FO%CA^HN`cxwni?*?^I8;o%^2I|#b!@H
z!~kFZVrVLm*xR}zG$0!nJB)j{!+gufR3EieNl0$mvb9e%%PXc-huMH^XTw*p?1
zYyBDhW(uaF%N2hMyCTWakzvUi@hY_+R8p{u`b*vcrP^U
z_*g|+yWK|d2olI`sQ^ThBwo*25*7;P@yH3tB(f9HU$-isz0RnuWHIEzUyNIb?n@Re
zv$Du(b|ul3b3Fq0U>?6%DxBrqHZ@M!(Q9Sr<$XXSD&RZR=lmi8#WaVOpR03FJ!gJX7}xq)vi!L65L~h`COI7w7PQN!xMG^TmKZsOTAK%u
z#7EYSymBa>Y&`4@Ffm&lxog|JGhG>BPx$u;Ig
zhanra)@5TBV{@8(le)od=MZScTHK2=8cikHIuNW>^0PQLiQ-@U95r?P0sc?spnX8XB-Fwp8ZN9nk*gQNY==j2)0kCP>
zDS3wH9LV%ani_3bU2|xy#zAU$rwL<`uAe~6y>{(&G8kQVUiZh>m`rur~bZ0XVL~QQ(q<_ClM)5o8+`+95hA?X0lOj&2f6?i%}xEm~y3R
zZA1w3h^*;MJ*GFdRrP9o(a}EeSy$0MRB1H>ND#EI?o(ILX|D1yXsML7Jz;PiQelZ+
zp!i9t0BZQ}Y0c!zH|4A21GdDR7i)Cpg{XY}^=@lm1vWb9>y^p4F^Fj{5|XH~U(`1y
zf0U&kUb4c0uQ(#`!MNRwE;%*DP}`saRhM}Q@8)WSInEKkDq_N)ih@A^4cDIuzpTR1
zg1^TRqQx;vVRq~}7XnA(a3&`_p-X}Rp+M!R82&a9yRuU2)qbcH!*(OuBG-ZxL$7^3
zk&b$I^~5I@OdQRRR`nvwa|Z8Ax*#R#RSH|9#$u7?>1oDhG*RHFDlwSr4bi&61QLwz
zDLzl|vh{cbR+{+2Riced&uLkYy9`dK_ScE8u`N&ueqg2cUruA%=)P)#35CF58vwV>
zIFPBlmMmvWShXzwjAC;X9Q9dnE`&F@@U8Utn=nx1ySEfLX(0((;LiiMhO*{o
z332vyIVs;A+_1A?y(oW|?Fl2oUa(^_iON_+oYqiYgd}-iq2eyFl8e*2C7b|Q$7#)w
zm1s2=sH^Fdv2u>d+BWU{?4KqFr-5CP>KbEH1xpYDVVij6M-c8AG=ym^@?d!I(P`9u
z(W@77VDq{wy0<#R`)C@Tr;x*YPD61$^u=U&KnFrtLk+}c7XYQ}!}&%5t49-o8#I6j
z8$BWc@|_PmISg)MZFq}`=(Tu&Y0*gn=!zUT%R6}HnzGC1I3zr#o#GHqMQG@>OzQj7okNAF
z(psjhjkl6sE-6TI^GhnVg0K&Qnd~;28l$D{!$=pSZL9m)_hz5f__8{k;McQxsl7yL
zoV4+ZL@DetHhsB+u&|Sr*#=j%+t!eitu!F$RMK>tLL_&GeKR_!oe^eQ=FnS3U9fs4
zI?FrCXlH>RT``+eW}G!(+Yec7JR&Y?WJi(
zmoa%r*|6?kWI2MyMWFR&UR94W?=gsTJxJ}_*g_YkdUWL!owBrj-lX=Hx;)8+BIbFr
zftcCqOWQ7{96mH7cGBrD==xgg7+$j^gyKT_a)O9QZ?{T>TX!jrkd>J#Cm|;2;tO2|
z=43{SY5NJhTQKQ*&oeNy$u#WO!de&b$r+usOzH|f+vA&o_9PCcYXVad((7s>b=O!Z
zxvTY)LL%1i&SDV@+C7(o`!I)3_ln}{m?q?=Y~@fKh>zj!lY5>N_O3$Ml2U5KPx+(7
zN0LYrf4JaN?NRvbXSVht{+PCc8`(XyfG??_f2D8e;jKH>`WI|T!;WbjqP9zrm*ZR7KW`bM%aMZ4>;lijsSslVlc+pT}&WfxFuQSMv0}uM1%mqJA$7GWa
z6pIIode$f6LrBHlm1tMmunGE`=P4W`HIGYvT#t8kYINF0AA{{c=jGrCMA7YO`<&7m
znPRW=3T+R(iyAEZD5LAgt+0a^)JQ95Y}
zArV<65fxQBr;(Bl?f2HlYs0
ziGdJ%;O|#epl^W;RG_kRG@~>7OHhi=$l8MLJ1b@ZM>7{2pdviba?Qm47dPlXx4gn5
zAS((u#k2^#&-gl#^es}6f5-WyC+g41pS(8g(F7)M06uYiwe0*BfoQ)={+9!*<1+zM
zpe4zFKtG#={Y
zWh|VWfPQ@cp#n$BpCHi$Fq3A1NJ*f0`j5@bc=iX#zgcbujwXNJ%$8|Sv|Ql8_W^R*
zf9Tq6-~sy2ga7Yw^MCDC&}KYbVj#*CIDmc}rk9j|j8g*IG1;2^%l?~tkPAUa!
zoPSK-#rj{#|LUpVSk(V~Fn@15{M8crTjX;6d-DGbxPRIH@BK7?9A#=eKOijruWrUa
zH|Ben#;-;|-(p?xH>CfwTj$T*@7>LQyk=br|G@pFquD<@LjKJ8-uCLNSK7B=k^Fbg
zA3CS~4E^4B>8qpGw|FJ}1N48^U;fBn>u1XM)-XTrI(OM$QvTNt=KtpC^fUK+i;S=aQLge^)V~~G-zzMBoxuDS=O(|*`v;1gKX3c@GJ`*k
za60qfF#ev4`Df+EpE=)Gb$=Bt{1(v`f5!Qj&icO6_{Yu)@%|;?4@$*8G>EADkeqE{m7WL`BO#91q`=2-V`_;N1uP(+}zs&l(<<*~)e?RN~b;0jj
z5a;|l`5!F*{S5hjw(!SY+EDOI$ls&#chmVlGroU@`a19UEsRQj$M}a?NO>s;-~$;5
R2np~f1o-$>Q}y+){|A@R9n$~+

literal 59203
zcma&O1CT9Y(k9%tZQHhO+qUh#ZQHhO+qmuS+qP|E@9xZO?0h@l{(r>DQ>P;GjjD{w
zH}lENr;dU&FbEU?00aa80D$0M0RRB{U*7-#kbjS|qAG&4l5%47zyJ#WrfA#1$1Ctx
zf&Z_d{GW=lf^w2#qRJ|CvSJUi(^E3iv~=^Z(zH}F)3Z%V3`@+rNB7gTVU{Bb~90p|f+0(v;nz01EG7yDMX9@S~__vVgv%rS$+?IH+oZ03D5zYrv|^
zC1J)SruYHmCki$jLBlTaE5&dFG9-kq3!^i>^UQL`%gn6)jz54$WDmeYdsBE9;PqZ_
zoGd=P4+|(-u4U1dbAVQrFWoNgNd;0nrghPFbQrJctO>nwDdI`Q^i0XJDUYm|T|RWc
zZ3^Qgo_Qk$%Fvjj-G}1NB#ZJqIkh;kX%V{THPqOyiq)d)0+(r9o(qKlSp*hmK#iIY
zA^)Vr$-Hz<#SF=0@tL@;dCQsm`V9s1vYNq}K1B)!XSK?=I1)tX+bUV52$YQu*0%fnWEukW>mxkz+%3-S!oguE8u#MGzST8_Dy^#U?fA@S#K$S@9msUiX!gd_ow>08w5)nX{-KxqMOo7d?k2&?Vf
z&diGDtZr(0cwPe9z9FAUSD9KC)7(n^lMWuayCfxzy8EZsns%OEblHFSzP=cL6}?J|
z0U$H!4S_TVjj<`6dy^2j`V`)mC;cB%*
z8{>_%E1^FH!*{>4a7*C1v>~1*@TMcLK{7nEQ!_igZC}ikJ$*<$yHy>7)oy79A~#xE
zWavoJOIOC$5b6*q*F_qN1>2#MY)AXVyr$6x4b=$x^*aqF*L?vmj>Mgv+|ITnw_BoW
zO?jwHvNy^prH{9$rrik1#fhyU^MpFqF2fYEt(;4`Q&XWOGDH8k6M=%@fics4ajI;st#
zCU^r1CK&|jzUhRMv;+W~6N;u<;#DI6cCw-otsc@IsN3MoSD^O`eNflIoR~l4*&-%RBYk@gb^|-JXs&~KuSEmMxB}xSb
z@K76cXD=Y|=I&SNC2E+>Zg?R6E%DGCH5J1nU!A|@eX9oS(WPaMm==k2s_ueCqdZw|
z&hqHp)47`c{BgwgvY2{xz%OIkY1xDwkw!<0veB#yF4ZKJyabhyyVS`gZepcFIk%e2
zTcrmt2@-8`7i-@5Nz>oQWFuMC_KlroCl(PLSodswHqJ3fn<;gxg9=}~3x_L3P`9Sn
zChIf}8vCHvTriz~T2~FamRi?rh?>3bX1j}%bLH+uFX+p&+^aXbOK7clZxdU~6Uxgy
z8R=obwO4dL%pmVo*Ktf=lH6hnlz_5k3cG;m8lgaPp~?eD!Yn2kf)tU6PF{kLyn|oI@eQ`F
z3IF7~Blqg8-uwUuWZScRKn%c2_}dXB6Dx_&xR*n9M9LXasJhtZdr$vBY!rP{c@=)&
z#!?L$2UrkvClwQO>U*fSMs67oSj2mxiJ$t;E|>q%Kh_GzzWWO&3;ufU%2z%ucBU8H
z3WIwr$n)cfCXR&>tyB7BcSInK>=ByZA%;cVEJhcg<#6N{aZC4>K41XF>ZgjG`z_u&
zGY?;Ad?-sgiOnI`oppF1o1Gurqbi*;#x2>+SSV6|1^G@ooVy@fg?wyf@0Y!UZ4!}nGuLeC^l)6pwkh|oRY`s1Pm$>zZ3u-83T|9
zGaKJIV3_x+u1>cRibsaJpJqhcm%?0-L;2
zitBrdRxNmb0OO2J%Y&Ym(6*`_P3&&5Bw157{o7LFguvxC$4&zTy#U=W*l&(Q2MNO}
zfaUwYm{XtILD$3864IA_nn34oVa_g^FRuHL5wdUd)+W-p-iWCKe8m_cMHk+=?
zeKX)M?Dt(|{r5t7IenkAXo%&EXIb-i^w+0CX0D=xApC=|Xy(`xy+QG^UyFe
z+#J6h_&T5i#sV)hj3D4WN%z;2+jJcZxcI3*CHXGmOF3^)JD5j&wfX)e?-|V0GPuA+
zQFot%aEqGNJJHn$!_}#PaAvQ^{3-Ye7b}rWwrUmX53(|~i0v{}G_sI9uDch_brX&6
zWl5Ndj-AYg(W9CGfQf<6!YmY>Ey)+uYd_JNXH=>|`OH-CDCmcH(0%iD_aLlNHKH
z7bcW-^5+QV$jK?R*)wZ>r9t}loM@XN&M-Pw=F#xn(;u3!(3SXXY^@=aoj70;_=QE9
zGghsG3ekq#N||u{4We_25U=y#T*S{4I{++Ku)>
zQ!DZW;pVcn>b;&g2;YE#+V`v*Bl&Y-i@X6D*OpNA{G@JAXho&aOk(_j^weW{#3X5Y
z%$q_wpb07EYPdmyH(1^09i$ca{O<}7)
zRWncXdSPgBE%BM#by!E>tdnc$8RwUJg1*x($6$}ae$e9Knj8gvVZe#bLi!<+&BkFj
zg@nOpDneyc+hU9P-;jmOSMN|*H#>^Ez#?;%C3hg_65leSUm;iz)UkW)jX#p)e&S&M
z1|a?wDzV5NVnlhRBCd_;F87wp>6c<&nkgvC+!@KGiIqWY4l}=&1w7|r6{oBN8xyzh
zG$b#2=RJp_iq6)#t5%yLkKx(0@D=C3w+oiXtSuaQ%I1WIb-eiE$d~!)b@|4XLy!CZ
z9p=t=%3ad@Ep+<9003D2KZ5VyP~_n$=;~r&YUg5UZ0KVD&tR1DHy9x)qWtKJp#Kq#
zP*8p#W(8JJ_*h_3W}FlvRam?<4Z+-H77^$Lvi+#vmhL9J
zJ<1SV45xi;SrO2f=-OB(7#iNA5)x1uNC-yNxUw|!00vcW2PufRm>e~toH;M0Q85MQLWd?3O{i8H+5VkR@l9Dg-ma
ze2fZ%>G(u5(k9EHj2L6!;(KZ8%8|*-1V|B#EagbF(rc+5iL_5;Eu)L4Z-V;0HfK4d
z*{utLse_rvHZeQ>V5H=f78M3Ntg1BPxFCVD{HbNA6?9*^YIq;B-DJd{Ca2L#)qWP?
zvX^NhFmX?CTWw&Ns}lgs;r3i+Bq@y}Ul+U%pzOS0Fcv9~aB(0!>GT0)NO?p=25LjN
z2bh>6RhgqD7bQj#k-KOm@JLgMa6>%-ok1WpOe)FS^XOU{c?d5shG(lIn3GiVBxmg`u%-j=)^v&pX1JecJics3&jvPI)mDut52?
z3jEA)DM%}BYbxxKrizVYwq?(P&19EXlwD9^-6J+4!}9{ywR9Gk42jjAURAF&EO|~N
z)?s>$Da@ikI4|^z0e{r`J8zIs>SpM~Vn^{3fArRu;?+43>lD+^XtUcY1HidJwnR6+
z!;oG2=B6Z_=M%*{z-RaHc(n|1RTKQdNjjV!Pn9lFt^4w|AeN06*j}ZyhqZ^!-=cyGP_ShV1rGxkx8t
zB;8`h!S{LD%ot``700d0@Grql(DTt4Awgmi+Yr0@#jbe=2#UkK%rv=OLqF)9D7D1j
z!~McAwMYkeaL$~kI~90)5vBhBzWYc3Cj1WI0RS`z000R8-@ET0dA~*r(gSiCJmQMN&4%1D
zyVNf0?}sBH8zNbBLn>~(W{d3%@kL_eQ6jEcR{l>C|JK
z(R-fA!z|TTRG40|zv}7E@PqCAXP3n`;%|SCQ|ZS%ym$I{`}t3KPL&^l5`3>yah4*6
zifO#{VNz3)?ZL$be;NEaAk9b#{tV?V7
zP|wf5YA*1;s<)9A4~l3BHzG&HH`1xNr#%){4xZ!jq%o=7nN*wMuXlFV{HaiQLJ`5G
zBhDi#D(m`Q1pLh@Tq+L;OwuC52RdW7b8}~60WCOK5iYMUad9}7aWBuILb({5=z~YF
zt?*Jr5NG+WadM{mDL>GyiByCuR)hd
zA=HM?J6l1Xv0Dl+LW@w$OTcEoOda^nFCw*Sy^I@$sSuneMl{4ys)|RY#9&NxW4S)9
zq|%83IpslTLoz~&vTo!Ga@?rj_kw{|k{nv+w&Ku?fyk4Ki4I?);M|5Axm)t+BaE)D
zm(`AQ#k^DWrjbuXoJf2{Aj^KT
zFb1zMSqxq|vceV+Mf-)$oPflsO$@*A0n0Z!R{&(xh8s}=;t(lIy
zv$S8x>m;vQNHuRzoaOo?eiWFe{0;$s`Bc+Osz~}Van${u;g(su`3lJ^TEfo~nERfP
z)?aFzpDgnLYiERsKPu|0tq4l2wT)Atr6Qb%m-AUn6HnCue*yWICp7TjW$@sO
zm5rm4aTcPQ(rfi7a`xP7cKCFrJD}*&_~xgLyr^-bmsL}y;A5P|al8J3WUoBSjqu%v
zxC;mK!g(7r6RRJ852Z~feoC&sD3(6}^5-uLK8o)9{8L_%%rItZK9C){UxB|;G>JbP
zsRRtS4-3B*5c+K2kvmgZK8472%l>3cntWUOVHxB|{Ay~aOg5RN;{PJgeVD*H%ac+y!h#wi%o2bF2Ca8IyMyH{>4#{E_8u^@+l-+n=V}Sq?$O
z{091@v%Bd*3pk0^2UtiF9Z+(a@wy6
zUdw8J*ze$K#=$48IBi1U%;hmhO>lu!uU;+RS}p&6@rQila7WftH->*A4=5W|Fmtze
z)7E}jh@cbmr9iup^i%*(uF%LG&!+Fyl@LFA-}Ca#bxRfDJAiR2dt6644TaYw1Ma79
zt8&DYj31j^5WPNf5P&{)J?WlCe@<3u^78wnd(Ja4^a>{^Tw}W>|Cjt^If|7l^l)^Q
zbz|7~CF(k_9~n|h;ysZ+jHzkXf(*O*@5m
zLzUmbHp=x!Q|!9NVXyipZ3)^GuIG$k;D)EK!a5=8MFLI_lpf`HPKl=-Ww%z8H_0$j
ztJ||IfFG1lE9nmQ0+jPQy
zCBdKkjArH@K7jVcMNz);Q(Q^R{d5G?-kk;Uu_IXSyWB)~KGIizZL(^&qF;|1PI7!E
zTP`%l)gpX|OFn&)M%txpQ2F!hdA~hX1Cm5)IrdljqzRg!f{mN%G~H1&oqe`5eJCIF
zHdD7O;AX-{XEV(a`gBFJ9ews#CVS2y!&>Cm_dm3C8*n3MA*e67(WC?uP@8TXuMroq
z{#w$%z@CBIkRM7?}Xib+>hRjy?%G!fiw8!
z8(gB+8J~KOU}yO7UGm&1g_MDJ$IXS!`+*b*QW2x)9>K~Y*E&bYMnjl6h!{17_8d!%&9D`a7r&LKZjC<&XOvTRaKJ1
zUY@hl5^R&kZl3lU3njk`3dPzxj$2foOL26r(9zsVF3n_F#v)s5vv3@dgs|lP#eylq62{<-vczqP!RpVBTgI>@O6&sU>W|do17+#OzQ7o5A$ICH
z?GqwqnK^n2%LR;$^oZM;)+>$X3s2n}2jZ7CdWIW0lnGK-b#EG01)P@aU`pg}th&J-TrU`tIpb5t((0eu|!u
zQz+3ZiOQ^?RxxK4;zs=l8q!-n7X{@jSwK(iqNFiRColuEOg}!7cyZi`iBX4g1pNBj
zAPzL?P^Ljhn;1$r8?bc=#n|Ed7wB&oHcw()&*k#SS#h}jO?ZB246EGItsz*;^&tzp
zu^YJ0=lwsi`eP_pU8}6JA7MS;9pfD;DsSsLo~ogzMNP70@@;Fm8f0^;>$Z>~}GWRw!W5J3tNX*^2+1f3hz{~rIzJo
z6W%J(H!g-eI_J1>0juX$X4Cl6i+3wbc~k146UIX&G22}WE>0ga#WLsn9tY(&29zBvH1$`iWtTe
zG2jYl@P!P)eb<5DsR72BdI7-zP&cZNI{7q3e@?N8IKc4DE#UVr->|-ryuJXk^u^>4
z$3wE~=q390;XuOQP~TNoDR?#|NSPJ%sTMInA6*rJ%go|=YjGe!B>z6u$IhgQSwoV*
zjy3F2#I>uK{42{&IqP59)Y(1*Z>>#W8rCf4_eVsH)`v!P#^;BgzKDR`ARGEZzkNX+
zJUQu=*-ol=Xqqt5=`=pA@BIn@6a9G8C{c&`i^(i+BxQO9?YZ3iu%$$da&Kb?2kCCo
zo7t$UpSFWqmydXf@l3bVJ=%K?SSw)|?srhJ-1ZdFu*5QhL$~-IQS!K1s@XzAtv6*Y
zl8@(5BlWYLt1yAWy?rMD&bwze8bC3-GfNH=p
zynNFCdxyX?K&G(ZZ)afguQ2|r;XoV^=^(;Cku#qYn4Lus`UeKt6rAlFo_rU`|Rq
z&G?~iWMBio<78of-2X(ZYHx~=U0Vz4btyXkctMKdc9UM!vYr~B-(>)(Hc|D
zMzkN4!PBg%tZoh+=Gba!0++d193gbMk2&krfDgcbx0jI92cq?FFESVg0D$>F+bil}
zY~$)|>1HZsX=5sAZ2WgPB5P=8X#TI+NQ(M~GqyVB53c6IdX=k>Wu@A0Svf5#?uHaF
zsYn|koIi3$(%GZ2+G+7Fv^lHTb#5b8sAHSTnL^qWZLM<(1|9|QFw9pnRU{svj}_Al
zL)b9>fN{QiA($8peNEJyy`(a{&uh-T4_kdZFIVsKKVM(?05}76EEz?#W
za^fiZOAd14IJ4zLX-n7Lq0qlQ^lW8Cvz4UKkV9~P}>sq0?xD3vg+$4vLm~C(+
zM{-3Z#qnZ09bJ>}j?6ry^h+@PfaD7*jZxBEY4)UG&daWb??6)TP+|3#Z&?GL?1i+280CFsE|vIXQbm|
zM}Pk!U`U5NsNbyKzkrul-DzwB{X?n3E6?TUHr{M&+R*2%yOiXdW-_2Yd6?38M9Vy^
z*lE%gA{wwoSR~vN0=no}tP2Ul5Gk5M(Xq`$nw#ndFk`tcpd5A=Idue`XZ!FS>Q
zG^0w#>P4pPG+*NC9gLP4x2m=cKP}YuS!l^?sHSFftZy{4CoQrb_
z^20(NnG`wAhMI=eq)SsIE~&Gp9Ne0nD4%Xiu|0Fj1UFk?6avDqjdXz{O1nKao*46y
zT8~iA%Exu=G#{x=KD;_C&M+Zx4+n`sHT>^>=-1YM;H<72k>$py1?F3#T1*ef9mLZw
z5naLQr?n7K;2l+{_uIw*_1nsTn~I|kkCgrn;|G~##hM;9l7Jy$yJfmk+&}W@JeKcF
zx@@Woiz8qdi|D%aH3XTx5*wDlbs?dC1_nrFpm^QbG@wM=i2?Zg;$VK!c^Dp8<}BTI
zyRhAq@#%2pGV49*Y5_mV4+OICP|%I(dQ7x=6Ob}>EjnB_-_18*xrY?b%-yEDT(wrO
z9RY2QT0`_OpGfMObKHV;QLVnrK%mc?$WAdIT`kJQT^n%GuzE7|9@k3ci5fYOh(287
zuIbg!GB3xLg$YN=n)^pHGB0jH+_iIiC=nUcD;G6LuJsjn2VI1cyZx=a?ShCsF==QK
z;q~*m&}L<-cb+mDDXzvvrRsybcgQ;Vg21P(uLv5I+eGc7o7tc6`;OA9{soHFOz
zT~2?>Ts}gprIX$wRBb4yE>ot<8+*Bv`qbSDv*VtRi|cyWS>)Fjs>fkNOH-+PX&4(~
z&)T8Zam2L6puQl?;5zg9h<}k4#|yH9czHw;1jw-pwBM*O2hUR6yvHATrI%^mvs9q_
z&ccT0>f#eDG<^WG^q@oVqlJrhxH)dcq2cty@l3~|5#UDdExyXUmLQ}f4#;6fI{f^t
zDCsgIJ~0`af%YR%Ma5VQq-p21k`vaBu6WE?66+5=XUd%Ay%D$irN>5LhluRWt7
zov-=f>QbMk*G##&DTQyou$s7UqjjW@k6=!I@!k+S{pP8R(2=e@io;N8E`EOB;OGoI
zw6Q+{X1_I{OO0HPpBz!X!@`5YQ2)t{+!?M_iH25X(d~-Zx~cXnS9z>u?+If|iNJbx
zyFU2d1!ITX64D|lE0Z{dLRqL1Ajj=CCMfC4lD3&mYR_R_VZ>_7_~|<^o*%_&jevU+
zQ4|qzci=0}Jydw|LXLCrOl1_P6Xf@c0$ieK2^7@A9UbF{@V_0p%lqW|L?5k>bVM8|p5v&2g;~r>B8uo<4N+`B
zH{J)h;SYiIVx@#jI&p-v3dwL5QNV1oxPr8J%ooezTnLW>i*3Isb49%5i!&ac_dEXv
zvXmVUck^QHmyrF8>CGXijC_R-y(Qr{3Zt~EmW)-nC!tiH`wlw5D*W7Pip;T?&j%kX
z6DkZX4&}iw>hE(boLyjOoupf6JpvBG8}jIh!!VhnD0>}KSMMo{1#uU6kiFcA04~|7
zVO8eI&x1`g4CZ<2cYUI(n#wz2MtVFHx47yE5eL~8bot~>EHbevSt}LLMQX?odD{Ux
zJMnam{d)W4da{l7&y-JrgiU~qY3$~}_F#G7|MxT)e;G{U`In&?`j<5D->}cb{}{T(4DF0BOk-=1195KB-E*o@c?`>y#4=dMtYtSY=&L{!TAjFVcq0y@AH`vH!
z$41+u!Ld&}F^COPgL(EE{0X7LY&%D7-(?!kjFF7=qw<;`V{nwWBq<)1QiGJgUc^Vz
ztMUlq1bZqKn17|6x6iAHbWc~l1HcmAxr%$Puv!znW)!JiukwIrqQ00|H$Z)OmGG@=
zv%A8*4cq}(?qn4rN6o`$Y))(MyXr8R<2S^J+v(wmFmtac!%VOfN?&(8Nr!T@kV`N;
z*Q33V3t`^rN&aBiHet)18wy{*wi1=W!B%B-Q6}SCrUl$~Hl{@!95ydml@FK8P=u4s
z4e*7gV2s=YxEvskw2Ju!2%{8h01rx-3`NCPc(O
zH&J0VH5etNB2KY6k4R@2Wvl^Ck$MoR3=)|SEclT2ccJ!RI9Nuter7u9@;sWf-%um;GfI!=eEIQ2l2p_YWUd{|6EG
ze{yO6;lMc>;2tPrsNdi@&1K6(1;|$xe8vLgiouj%QD%gYk`4p{Ktv9|j+!OF-P?@p
z;}SV|oIK)iwlBs+`ROXkhd&NK
zzo__r!B>tOXpBJMDcv!Mq54P+n4(@dijL^EpO1wdg~q+!DT3lB<>9AANSe!T1XgC=J^)IP0XEZ()_vpu!!3HQyJhwh?r`Ae%Yr~b%
zO*NY9t9#qWa@GCPYOF9aron7thfWT`eujS4`t2uG6)~JRTI;f(ZuoRQwjZjp5Pg34
z)rp$)Kr?R+KdJ;IO;pM{$6|2y=k_siqvp%)2||cHTe|b5Ht8&A{wazGNca
zX$Ol?H)E_R@SDi~4{d-|8nGFhZPW;Cts1;08TwUvLLv&_2$O6Vt=M)X;g%HUr$&06
zISZb(6)Q3%?;3r~*3~USIg=HcJhFtHhIV(siOwV&QkQe#J%H9&E21!C*d@ln3E@J*
zVqRO^<)V^ky-R|%{(9`l-(JXq9J)1r$`uQ8a}$vr9E^nNiI*thK8=&UZ0dsFN_eSl
z(q~lnD?EymWLsNa3|1{CRPW60>DSkY9YQ;$4o3W7Ms&@&lv9eH!tk~N&dhqX&>K@}
zi1g~GqglxkZ5pEFkllJ)Ta1I^c&Bt6#r(QLQ02yHTaJB~-
zCcE=5tmi`UA>@P=1LBfBiqk)HB4t8D?02;9eXj~kVPwv?m{5&!&TFYhu>3=_
zsGmYZ^mo*-j69-42y&Jj0cBLLEulNRZ9vXE)8~mt9C#;tZs;=#M=1*hebkS;7(aGf
zcs7zH(I8Eui9UU4L--))yy`&d&$In&VA2?DAEss4LAPCLd>-$i?lpXvn!gu^JJ$(DoUlc6wE98VLZ*z`QGQov5l4Fm_h?V-;mHLYDVOwKz7>e4+%AzeO>P6v}ndPW|
zM>m#6Tnp7K?0mbK=>gV}=@k*0Mr_PVAgGMu$j+pWxzq4MAa&jpCDU&-5eH27Iz>m^
zax1?*HhG%pJ((tkR(V(O(L%7v7L%!_X->IjS3H5kuXQT2!ow(;%FDE>16&3r){!ex
zhf==oJ!}YU89C9@mfDq!P3S4yx$aGB?rbtVH?sHpg?J5C->!_FHM%Hl3#D4eplxzQ
zRA+<@LD%LKSkTk2NyWCg7u=$%F#;SIL44~S_OGR}JqX}X+=bc@swpiClB`Zbz|f!4
z7Ysah7OkR8liXfI`}IIwtEoL}(URrGe;IM8%{>b1SsqXh)~w}P>yiFRaE>}rEnNkT
z!HXZUtxUp1NmFm)Dm@-{FI^aRQqpSkz}ZSyKR%Y}YHNzBk)ZIp}
zMtS=aMvkgWKm9&oTcU0?S|L~CDqA+sHpOxwnswF-fEG)cXCzUR?ps@tZa$=O)=L+5
zf%m58cq8g_o}3?Bhh+c!w4(7AjxwQ3>WnVi<{{38g7yFboo>q|+7qs<$8CPXUFAN<
zG&}BHbbyQ5n|qqSr?U~GY{@GJ{(Jny{bMaOG{|IkUj7tj^9pa9|FB_<+KHLxSxR;@
zHpS$4V)PP+tx}22fWx(Ku9y+}Ap;VZqD0AZW4gCDTPCG=zgJmF{|x;(rvdM|2|9a}cex6xrMkERnkE;}jvU-kmzd%_J50$M`lIPCKf+^*zL=@LW`1SaEc%=m
zQ+lT06Gw+wVwvQ9fZ~#qd430v2HndFsBa9WjD0P}K(rZYdAt^5WQIvb%D^Q|pkVE^
zte$&#~zmULFACGfS#g=2OLOnIf2Of-k!(BIHjs77nr!5Q1*I9
z1%?=~#Oss!rV~?-6Gm~BWJiA4mJ5TY&iPm_$)H1_rTltuU1F3I(qTQ^U$S>%$l
z)Wx1}R?ij0idp@8w-p!Oz{&*W;v*IA;JFHA9%nUvVDy7Q8woheC#|8QuDZb-L_5@R
zOqHwrh|mVL9b=+$nJxM`3eE{O$sCt$UK^2@L$R(r^-_+z?lOo+me-VW=Zw
z-Bn>$4ovfWd%SPY`ab-u9{INc*k2h+yH%toDHIyqQ
zO68=u`N}RIIs7lsn1D){)~%>ByF<>i@qFb<-axvu(Z+6t7v<^z&gm9McRB~BIaDn$
z#xSGT!rzgad8o>~kyj#h1?7g96tOcCJniQ+*#=b7wPio>|6a1Z?_(TS{)KrPe}(8j
z!#&A=k(&Pj^F;r)CI=Z{LVu>uj!_W1q4b`N1}E(i%;BWjbEcnD=mv$FL$l?zS6bW!{$7j1GR5ocn94P2u{
z70tAAcpqtQo<@cXw~@i-@6B23;317|l~S>CB?hR5qJ%J3EFgyBdJd^fHZu7AzHF(BQ!tyAz^L0`X
z23S4Fe{2X$W0$zu9gm%rg~A>ijaE#GlYlrF9$ds^QtaszE#4M(OLVP2O-;XdT(XIC
zatwzF*)1c+t~c{L=fMG8Z=k5lv>U0;C{caN1NItnuSMp)6G3mbahu>E#sj&oy94KC
zpH}8oEw{G@N3pvHhp{^-YaZeH;K+T_1AUv;IKD<=mv^&Ueegrb!yf`4VlRl$M?wsl
zZyFol(2|_QM`e_2lYSABpKR{{NlxlDSYQNkS;J66aT#MSiTx~;tUmvs-b*CrR4w=f
z8+0;*th6kfZ3|5!Icx3RV11sp=?`0Jy3Fs0N4GZQMN=8HmT6%x9@{Dza)k}UwL6JT
zHRDh;%!XwXr6yuuy`4;Xsn0zlR$k%r%9abS1;_v?`HX_hI|+EibVnlyE@3aL5vhQq
zlIG?tN^w@0(v9M*&L+{_+RQZw=o|&BRPGB>e5=ys7H`nc8nx)|-g;s7mRc7hg{GJC
zAe^vCIJhajmm7C6g!
zL&!WAQ~5d_5)00?w_*|*H>3$loHrvFbitw#WvLB!JASO?#5Ig5$Ys10n>e4|3d;tS
zELJ0|R4n3Az(Fl3-r^QiV_C;)lQ1_CW{5bKS15U|E9?ZgLec@%kXr84>5jV2a5v=w
z?pB1GPdxD$IQL4)G||B_lI+A=08MUFFR4MxfGOu07vfIm+j=z9tp~5i_6jb`tR>qV
z$#`=BQ*jpCjm$F0+F)L%xRlnS%#&gro6PiRfu^l!EVan|r3y}AHJQOORGx4~
z&<)3=K-tx518DZyp%|!EqpU!+X3Et7n2AaC5(AtrkW>_57i}$eqs$rupubg0a1+WO
zGHZKLN2L0D;ab%{_S1Plm|hx8R?O14*w*f&2&bB050n!R2by
zw!@XOQx$SqZ5I<(Qu$V6g>o#A!JVwErWv#(Pjx=KeS0@hxr4?13zj#oWwPS(7Ro|v
z>Mp@Kmxo79q|}!5qtX2-O@U&&@6s~!I&)1WQIl?lTnh6UdKT_1R640S4~f=_xoN3-
zI+O)$R@RjV$F=>Ti7BlnG1-cFKCC(t|Qjm{SalS~V-tX#+2ekRhwmN
zZr`8{QF6y~Z!D|{=1*2D-JUa<(1Z=;!Ei!KiRNH?o{p5o3crFF=_pX9O-YyJchr$~
zRC`+G+8kx~fD2k*ZIiiIGR<8r&M@3H?%JVOfE>)})7ScOd&?OjgAGT@WVNSCZ8N(p
zuQG~76GE3%(%h1*vUXg$vH{ua0b`sQ4f0*y=u~lgyb^!#CcPJa2mkSEHGLsnO^kb$
zru5_l#nu=Y{rSMWiYx?nO{8I!gH+?wEj~UM?IrG}E|bRIBUM>UlY<`T1EHpRr36vv
zBi&dG8oxS|J$!zoaq{+JpJy+O^W(nt*|#g32bd&K^w-t>!Vu9N!k9eA8r!Xc{utY>
zg9aZ(D2E0gL#W0MdjwES-7~Wa8iubPrd?8-$C4BP?*wok&O8+ykOx{P=Izx+G~hM8
z*9?BYz!T8~dzcZr#ux8kS7u7r@A#DogBH8km8Ry4slyie^n|GrTbO|cLhpqgMdsjX
zJ_LdmM#I&4LqqsOUIXK8gW;V0B(7^$y#h3h>J0k^WJfAMeYek%Y-Dcb_+0zPJez!GM
zAmJ1u;*rK=FNM0Nf}Y!!P9c4)HIkMnq^b;JFd!S3?_Qi2G#LIQ)TF|iHl~WKK6JmK
zbv7rPE6VkYr_%_BT}CK8h=?%pk@3cz(UrZ{@h40%XgThP*-Oeo`T0eq9
zA8BnWZKzCy5e&&_GEsU4*;_k}(8l_&al5K-V*BFM=O~;MgRkYsOs%9eOY6s6AtE*<7GQAR2ulC3RAJrG_P1iQK5Z~&B
z&f8X<>yJV6)oDGIlS$Y*D^Rj(cszTy5c81a5IwBr`BtnC6_e`ArI8CaTX_%rx7;cn
zR-0?J_LFg*?(#n~G8cXut(1nVF0Oka$A$1FGcERU<^ggx;p@CZc?3UB41RY+wLS`LWFNSs~YP
zuw1@DNN3lTd|jDL7gjBsd9}wIw}4xT2+8dBQzI00m<@?c2L%>}QLfK5%r!a-iII`p
zX@`VEUH)uj^$;7jVUYdADQ2k*!1O3WdfgF?OMtUXNpQ1}QINamBTKDuv19^{$`8A1
zeq%q*O0mi@(%sZU>Xdb0Ru96CFqk9-L3pzLVsMQ`Xpa~N6CR{9Rm2)A|CI21L(%GW
zh&)Y$BNHa=FD+=mBw3{qTgw)j0b!Eahs!rZnpu)z!!E$*eXE~##yaXz`KE5(nQM`s
zD!$vW9XH)iMxu9R>r$VlLk9oIR%HxpUiW=BK@4U)|1WNQ=mz9a
z^!KkO=>GaJ!GBXm{KJj^;kh-MkUlEQ%lza`-G&}C5y1>La1sR6hT=d*NeCnuK%_LV
zOXt$}iP6(YJKc9j-Fxq~*ItVUqljQ8?oaysB-EYtFQp9oxZ|5m0^Hq(qV!S+hq#g(
z?|i*H2MIr^Kxgz+3vIljQ*Feejy6S4v~jKEPTF~Qhq!(ms5>NGtRgO5vfPPc4Z^AM
zTj!`5xEreIN)vaNxa|q6qWdg>+T`Ol0Uz)ckXBXEGvPNEL3R8hB3=C5`@=SYgAju1
z!)UBr{2~=~xa{b8>x2@C7weRAEuatC)3pkRhT#pMPTpSbA|tan%U7NGMvzmF?c!V8
z=pEWxbdXbTAGtWTyI?Fml%lEr-^AE}w#l(<7OIw;ctw}imYax&vR4UYNJZK6P7ZOd
zP87XfhnUHxCUHhM@b*NbTi#(-8|wcv%3BGNs#zRCVV(W?1Qj6^PPQa<{yaBwZ`+<`w|;rqUY_C
z&AeyKwwf*q#OW-F()lir=T^<^wjK65Lif$puuU5+tk$;e_EJ;Lu+pH>=-8=PDhkBg
z8cWt%@$Sc#C6F$Vd+0507;{OOyT7Hs%nKS88q-W!$f~9*WGBpHGgNp}=C*7!RiZ5s
zn1L_DbKF@B8kwhDiLKRB@lsXVVLK|ph=w%_`#owlf@s@V(pa`GY$8h%;-#h@TsO|Y8V=n@*!Rog7<7Cid%apR|x
zOjhHCyfbIt%+*PCveTEcuiDi%Wx;O;+K=W?OFUV%)%~6;gl?<0%)?snDDqIvkHF{
zyI02)+lI9ov42^hL>ZRrh*HhjF9B$A@=H94iaBESBF=eC_KT$8A@uB^6$~o?3Wm5t1OIaqF^~><2?4e3c&)@wKn9bD?
zoeCs;H>b8DL^F&>Xw-xjZEUFFTv>JD^O#1E#)CMBaG4DX9bD(Wtc8Rzq}9soQ8`jf
zeSnHOL}<+WVSKp4kkq&?SbETjq6yr@4%SAqOG=9E(3YeLG9dtV+8vmzq+6PFPk{L;
z(&d++iu=^F%b+ea$i2UeTC{R*0Isk;vFK!no<;L+(`y`3&H-~VTdKROkdyowo1iqR
zbVW(3`+(PQ2>TKY>N!jGmGo7oeoB8O|P_!Ic@
zZ^;3dnuXo;WJ?S+)%P>{Hcg!Jz#2SI(s&dY4QAy_vRlmOh)QHvs_7c&zkJCmJGVvV
zX;Mtb>QE+xp`KyciG$Cn*0?AK%-a|=o!+7x&&yzHQOS>8=B*R=niSnta^Pxp1`=md
z#;$pS$4WCT?mbiCYU?FcHGZ#)kHVJTTBt^%XE(Q};aaO=Zik0UgLcc0I(tUpt(>|&
zcxB_|fxCF7>&~5eJ=Dpn&5Aj{A^cV^^}(7w#p;HG&Q)EaN~~EqrE1qKrMAc&WXIE;>@<&)5;gD2?={Xf@Mvn@OJKw=8Mgn
z!JUFMwD+s==JpjhroT&d{$kQAy%+d`a*XxDEVxy3`NHzmITrE`o!;5ClXNPb4t*8P
zzAivdr{j_v!=9!^?T3y?gzmqDWX6mkzhIzJ-3S{T5bcCFMr&RPDryMcdwbBuZbsgN
zGrp@^i?rcfN7v0NKGzDPGE#4yszxu=I_`MI%Z|10nFjU-UjQXXA?k8Pk|OE<(?ae)
zE%vG#eZAlj*E7_3dx#Zz4kMLj>H^;}33UAankJiDy5ZvEhrjr`!9eMD8COp}U*hP+
zF}KIYx@pkccIgyxFm#LNw~G&`;o&5)2`5aogs`1~7cMZQ7zj!%L4E`2yzlQN6REX20&O<9
zKV6fyr)TScJPPzNTC2gL+0x#=u>(({{D7j)c-%tvqls3#Y?Z1m
zV5WUE)zdJ{$p>yX;^P!UcXP?UD~YM;IRa#Rs5~l+*$&nO(;Ers`G=0D!twR(0GF@c
zHl9E5DQI}Oz74n
zfKP>&$q0($T4y$6w(p=ERAFh+>n%iaeRA%!T%<^+pg?M)@ucY<&59$x9M#n+V&>}=nO9wCV{O~lg&v#+jcUj(tQ
z`0u1YH)-`U$15a{pBkGyPL0THv1P|4e@pf@3IBZS4dVJPo#H>pWq%Lr0YS-SeWash
z8R7=jb28KPMI|_lo#GEO|5B?N_e``H*23{~a!AmUJ+fb4HX-%QI@lSEUxKlGV7z7Q
zSKw@-TR>@1RL%w{x}dW#k1NgW+q4yt2Xf1J62Bx*O^WG8OJ|FqI4&@d3_o8Id@*)4
zYrk=>@!wv~mh7YWv*bZhxqSmFh2Xq)o=m;%n$I?GSz49l1$xRpPu_^N(vZ>*>Z<04
z2+rP70oM=NDysd!@fQdM2OcyT?3T^Eb@lIC-UG=Bw{BjQ&P`KCv$AcJ;?`vdZ4){d
z&gkoUK{$!$$K`3*O-jyM1~p-7T*qb)Ys>Myt^;#1&a%O@x8A+E>!
zY8=eD`ZG)LVagDLBeHg>=atOG?Kr%h4B%E6m@J^C+U|y)XX@f
z8oyJDW|9g=<#f<{JRr{y#~euMnv)`7j=%cHWLc}ngjq~7k**6%4u>Px&W%4D94(r*
z+akunK}O0DC2A%Xo9jyF;DobX?!1I(7%}@7F>i%&nk*LMO)bMGg2N+1iqtg+r(70q
zF5{Msgsm5GS7DT`kBsjMvOrkx&|EU!{{~gL4d2MWrAT=KBQ-^zQCUq{5PD1orxlIL
zq;CvlWx#f1NWvh`hg011I%?T_s!e38l*lWVt|~z-PO4~~1g)SrJ|>*tXh=QfXT)%(
z+ex+inPvD&O4Ur;JGz>$sUOnWdpSLcm1X%aQDw4{dB!cnj`^muI$CJ2%p&-kULVCE
z>$eMR36kN$wCPR+OFDM3-U(VOrp9k3)lI&YVFqd;Kpz~K)@Fa&FRw}L(SoD
z9B4a+hQzZT-BnVltst&=kq6Y(f^S4hIGNKYBgMxGJ^;2yrO}P3;r)(-I-CZ)26Y6?
z&rzHI_1GCvGkgy-t1E;r^3Le30|%$ebDRu2+gdLG)r=A~Qz`}~&L@aGJ{}vVs_GE*
zVUjFnzHiXfKQbpv&bR&}l2bzIjAooB)=-XNcYmrGmBh(&iu@o!^hn0^#}m2yZZUK8
zufVm7Gq0y`Mj;9b>`c?&PZkU0j4>IL=UL&-Lp3j&47B5pAW4JceG{!XCA)kT<%2nqCxj<)uy6XR_uws~>_MEKPOpAQ!H
zkn>FKh)<9DwwS*|Y(q?$^N!6(51O0
z^JM~Ax{AI1Oj$fs-S5d4T7Z_i1?{%0SsIuQ&r8#(JA=2iLcTN+?>wOL532%&dMYkT
z*T5xepC+V6zxhS@vNbMoi|i)=rpli@R9~P!39tWbSSb904ekv7D#quKbgFEMTb48P
zuq(VJ+&L8aWU(_FCD$3^uD!YM%O^K(dvy~Wm2hUuh6bD|#(I39Xt>N1Y{ZqXL`Fg6
zKQ?T2htHN!(Bx;tV2bfTtIj7e)liN-29s1kew>v(D^@)#v;}C4-G=7x#;-dM4yRWm
zyY`cS21ulzMK{PoaQ6xChEZ}o_#}X-o}<&0)$1#3we?+QeLt;aVCjeA)hn!}UaKt<
zat1fHEx13y-rXNMvpUUmCVzocPmN~-Y4(YJvQ#db)4|%B!rBsgAe+*yor~}FrNH08
z3V!97S}D7d$zbSD{$z;@IYMxM6aHdypIuS*pr_U6;#Y!_?0i|&yU*@16l
z*dcMqDQgfNBf}?quiu4e>H)yTVfsp#f+Du0@=Kc41QockXkCkvu>FBd6Q+@FL!(Yx
z2`YuX#eMEiLEDhp+9uFqME_E^faV&~9qjBHJkIp~%$x^bN=N)K@kvSVEMdDuzA0sn
z88CBG?`RX1@#hQNd`o^V{37)!w|nA)QfiYBE^m=yQKv-fQF+UCMcuEe1d4BH7$?>b
zJl-r9@0^Ie=)guO1vOd=i$_4sz>y3x^R7n4ED!5oXL3@5**h(xr%Hv)_gILarO46q+MaDOF%ChaymKoI6JU5Pg;7#2n9-18|S1;AK+
zgsn6;k6-%!QD>D?cFy}8F;r@z8H9xN1jsOBw2vQONVqBVEbkiNUqgw~*!^##ht>w0
zUOykwH=$LwX2j&nLy=@{hr)2O&-wm-NyjW7n~Zs9UlH;P7iP3
zI}S(r0YFVYacnKH(+{*)Tbw)@;6>%=&Th=+Z6NHo_tR|JCI8TJiXv2N7ei7M^Q+RM
z?9o`meH$5Yi;@9XaNR#jIK^&{N|DYNNbtdb)XW1Lv2k{E>;?F`#Pq|&_;gm~&~Zc9
zf+6ZE%{x4|{YdtE?a^gKyzr}dA>OxQv+pq|@IXL%WS0CiX!V
zm$fCePA%lU{%pTKD7|5NJHeXg=I0jL@$tOF@K*MI$)f?om)D63K*M|r`gb9edD1~Y
zc|w7N)Y%do7=0{RC|AziW7#am$)9jciRJ?IWl9PE{G3U+$%FcyKs_0Cgq`=K3@ttV
z9g;M!3z~f_?P%y3-ph%vBMeS@p7P&Ea8M@97+%XEj*(1E6vHj==d
zjsoviB>j^$_^OI_DEPvFkVo(BGRo%cJeD){6Uckei=~1}>sp299|IRjhXe)%?uP0I
zF5+>?0#Ye}T^Y$u_rc4=lPcq4K^D(TZG-w30-YiEM=dcK+4#o*>lJ8&JLi+3UcpZk
z!^?95S^C0ja^jwP`|{<+3cBVog$(mRdQmadS+Vh~z
zS@|P}=|z3P6uS+&@QsMp0no9Od&27O&14zHXGAOEy
zh~OKpymK5C%;LLb467@KgIiVwYbYd6wFxI{0-~MOGfTq$nBTB!{SrWmL9Hs}C&l&l#m?s*{tA?BHS4mVKHAVMqm63H<|c5n0~k)-kbg
zXidai&9ZUy0~WFYYKT;oe~rytRk?)r8bptITsWj(@HLI;@=v5|XUnSls7$uaxFRL+
zRVMGuL3w}NbV1`^=Pw*0?>bm8+xfeY(1PikW*PB>>Tq(FR`91N0c2&>lL2sZo5=VD
zQY{>7dh_TX98L2)n{2OV=T10~*YzX27i2Q7W86M4$?gZIXZaBq#sA*{PH8){|GUi;oM>e?ua7eF4WFuFYZSG|
zze?srg|5Ti8Og{O
zeFxuw9!U+zhyk?@w
zjsA6(oKD=Ka;A>Ca)oPORxK+kxH#O@zhC!!XS4@=swnuMk>t+JmLmFiE^1aX3f<)D@`%K0FGK^gg1a1j>zi
z2KhV>sjU7AX3F$SEqrXSC}fRx64GDoc%!u2Yag68Lw@w9v;xOONf@o)Lc|Uh3<21ctTYu-mFZuHk*+R{GjXHIGq3p)tFtQp%TYqD=j1&y)>@zxoxUJ!G@
zgI0XKmP6MNzw>nRxK$-Gbzs}dyfFzt>#5;f6oR27ql!%+{tr+(`(>%51|k`ML}
zY4eE)Lxq|JMas(;JibNQds1bUB&r}ydMQXBY4x(^&fY_&LlQC)3hylc$~8&~|06-D
z#T+%66rYbHX%^KuqJED_wuGB+=h`nWA!>1n0)3wZrBG3%`b^Ozv6__dNa@%V14|!D
zQ?o$z5u0^8`giv%qE!BzZ!3j;BlDlJDk)h@9{nSQeEk!z9RGW)
z${RSF3phEM*ce*>Xdp}585vj$|40=&S{S-GTiE?Op*vY&Lvr9}BO$XWy80IF+6@%n
z5*2ueT_g@ofP#u5pxb7n*fv^Xtt7&?SRc{*2Ka-*!BuOpf}neHGCiHy$@Ka1^Dint
z;DkmIL$-e)rj4o2WQV%Gy;Xg(_Bh#qeOsTM2f@KEe~4kJ8kNLQ+;(!j^bgJMcNhvklP5Z6I+9Fq@c&D~8Fb-4rmDT!MB5QC{Dsb;BharP*O;SF4&
zc$wj-7Oep7#$WZN!1nznc@Vb<_Dn%ga-O#J(l=OGB`dy=Sy&$(5-n3zzu%d7E#^8`T@}V+5B;PP8J14#4cCPw-SQTdGa2gWL0*zKM
z#DfSXs_iWOMt)0*+Y>Lkd=LlyoHjublNLefhKBv@JoC>P7N1_#>
zv=mLWe96%EY;!ZGSQDbZWb#;tzqAGgx~uk+-$+2_8U`!ypbwXl
z^2E-FkM1?lY@yt8=J3%QK+xaZ6ok=-y%=KXCD^0r!5vUneW>95PzCkOPO*t}p$;->
ze5j-BLT_;)cZQzR2CEsm@rU7GZfFtdp*a|g4wDr%8?2QkIGasRfDWT-Dvy*U{?IHT
z*}wGnzdlSptl#ZF^sf)KT|BJs&kLG91^A6ls{CzFprZ6-Y!V0Xysh%9p%iMd7HLsS
zN+^Un$tDV)T@i!v?3o0Fsx2qI(AX_$dDkBzQ@fRM%n
zRXk6hb9Py#JXUs+7)w@eo;g%QQ95Yq!K_d=z{0dGS+pToEI6=Bo8+{k$7&Z
zo4>PH(`ce8E-Ps&uv`NQ;U$%t;w~|@E3WVOCi~R4oj5wP?%<*1C%}Jq%a^q~T7u>K
zML5AKfQDv6>PuT`{SrKHRAF+^&edg6+5R_#H?Lz3iGoWo#PCEd0DS;)2U({{X#zU^
zw_xv{4x7|t!S)>44J;KfA|DC?;uQ($l+5Vp7oeqf7{GBF9356nx|&B~gs+@N^gSdd
zvb*>&W)|u#F{Z_b`f#GVtQ`pYv3#||N{xj1NgB<#=Odt6{eB%#9RLt5v
zIi|0u70`#ai}9fJjKv7dE!9ZrOIX!3{$z_K5FBd-Kp-&e4(J$LD-)NMTp^_pB`RT;
zftVVlK2g@+1Ahv2$D){@Y#cL#dUj9*&%#6
zd2m9{1NYp>)6=oAvqdCn5#cx{AJ%S8skUgMglu2*IAtd+z1>B&`MuEAS(D(<6X#Lj
z?f4CFx$)M&$=7*>9v1ER4b6!SIz-m0e{o0BfkySREchp?WdVPpQCh!q$t>?rL!&Jg
zd#heM;&~A}VEm8Dvy&P|J*eAV&w!&Nx6HFV&B8jJFVTmgLaswn!cx$&%JbTsloz!3
zMEz1d`k==`Ueub_JAy_&`!ogbwx27^ZXgFNAbx=g_I~5nO^r)}&myw~+yY*cJl4$I
znNJ32M&K=0(2Dj_>@39`3=FX!v3nZHno_@q^!y}%(yw0PqOo=);6Y@&ylVe>nMOZ~
zd>j#QQSBn3oaWd;qy$&5(5H$Ayi)0haAYO6TH>FR?rhqHmNOO+(})NB
zLI@B@v0)eq!ug`>G<@htRlp3n!EpU|n+G+AvXFrWSUsLMBfL*ZB`CRsIVHNTR&b?K
zxBgsN0BjfB>UVcJ|x%=-zb%OV7lmZc&
zxiupadZVF7)6QuhoY;;FK2b*qL0J-Rn-8!X4ZY$-ZSUXV5DFd7`T41c(#lAeLMoeT
z4%g655v@7AqT!i@)Edt5JMbN(=Q-6{=L4iG8RA%}w;&pKmtWvI4?G9pVRp|RTw`g0
zD5c12B&A2&P6Ng~8WM2eIW=wxd?r7A*N+&!Be7PX3s|7~z=APxm=A?5
zt>xB4WG|*Td@VX{Rs)PV0|yK`oI3^xn(4c_j&vgxk_Y3o(-`_5o`V
zRTghg6%l@(qodXN;dB#+OKJEEvhfcnc#BeO2|E(5df-!fKDZ!%9!^BJ_4)9P+9Dq5
zK1=(v?KmIp34r?z{NEWnLB3Px{XYwy-akun4F7xTRr2^zeYW{gcK9)>aJDdU5;w5@
zak=<+-PLH-|04pelTb%ULpuuuJC7DgyT@D|p{!V!0v3KpDnRjANN12q6SUR3mb9<-
z>2r~IApQGhstZ!3*?5V
z8#)hJ0TdZg0M-BK#nGFP>$i=qk82DO
z7h;Ft!D5E15OgW)&%lej*?^1~2=*Z5$2VX>V{x8SC+{i10BbtUk9@I#Vi&hX)q
Q!LwySI{Bnv%Sm)yh{^sSVJ8&h_D-BJ_YZe5eCaAWU9b$O2c z$T|{vWVRtOL!xC0DTc(Qbe`ItNtt5hr<)VijD0{U;T#bUEp381_y`%ZIav?kuYG{iyYdEBPW=*xNSc;Rlt6~F4M`5G+VtOjc z*0qGzCb@gME5udTjJA-9O<&TWd~}ysBd(eVT1-H82-doyH9RST)|+Pb{o*;$j9Tjs zhU!IlsPsj8=(x3bAKJTopW3^6AKROHR^7wZ185wJGVhA~hEc|LP;k7NEz-@4p5o}F z`AD6naG3(n=NF9HTH81=F+Q|JOz$7wm9I<+#BSmB@o_cLt2GkW9|?7mM;r!JZp89l zbo!Hp8=n!XH1{GwaDU+k)pGp`C|cXkCU5%vcH)+v@0eK>%7gWxmuMu9YLlChA|_D@ zi#5zovN_!a-0?~pUV-Rj*1P)KwdU-LguR>YM&*Nen+ln8Q$?WFCJg%DY%K}2!!1FE zDv-A%Cbwo^p(lzac&_TZ-l#9kq`mhLcY3h9ZTUVCM(Ad&=EriQY5{jJv<5K&g|*Lk zgV%ILnf1%8V2B0E&;Sp4sYbYOvvMebLwYwzkRQ#F8GpTQq#uv=J`uaSJ34OWITeSGo6+-8Xw znCk*n{kdDEi)Hi&u^)~cs@iyCkFWB2SWZU|Uc%^43ZIZQ-vWNExCCtDWjqHs;;tWf$v{}0{p0Rvxkq``)*>+Akq%|Na zA`@~-Vfe|+(AIlqru+7Ceh4nsVmO9p9jc8}HX^W&ViBDXT+uXbT#R#idPn&L>+#b6 zflC-4C5-X;kUnR~L>PSLh*gvL68}RBsu#2l`s_9KjUWRhiqF`j)`y`2`YU(>3bdBj z?>iyjEhe-~$^I5!nn%B6Wh+I`FvLNvauve~eX<+Ipl&04 zT}};W&1a3%W?dJ2=N#0t?e+aK+%t}5q%jSLvp3jZ%?&F}nOOWr>+{GFIa%wO_2`et z=JzoRR~}iKuuR+azPI8;Gf9)z3kyA4EIOSl!sRR$DlW}0>&?GbgPojmjmnln;cTqCt=ADbE zZ8GAnoM+S1(5$i8^O4t`ue;vO4i}z0wz-QEIVe5_u03;}-!G1NyY8;h^}y;tzY}i5 zqQr#Ur3Fy8sSa$Q0ys+f`!`+>9WbvU_I`Sj;$4{S>O3?#inLHCrtLy~!s#WXV=oVP zeE93*Nc`PBi4q@%Ao$x4lw9vLHM!6mn3-b_cebF|n-2vt-zYVF_&sDE--J-P;2WHo z+@n2areE0o$LjvjlV2X7ZU@j+`{*8zq`JR3gKF#EW|#+{nMyo-a>nFFTg&vhyT=b} zDa8+v0(Dgx0yRL@ZXOYIlVSZ0|MFizy0VPW8;AfA5|pe!#j zX}Py^8fl5SyS4g1WSKKtnyP+_PoOwMMwu`(i@Z)diJp~U54*-miOchy7Z35eL>^M z4p<-aIxH4VUZgS783@H%M7P9hX>t{|RU7$n4T(brCG#h9e9p! z+o`i;EGGq3&pF;~5V~eBD}lC)>if$w%Vf}AFxGqO88|ApfHf&Bvu+xdG)@vuF}Yvk z)o;~k-%+0K0g+L`Wala!$=ZV|z$e%>f0%XoLib%)!R^RoS+{!#X?h-6uu zF&&KxORdZU&EwQFITIRLo(7TA3W}y6X{?Y%y2j0It!ekU#<)$qghZtpcS>L3uh`Uj z7GY;6f$9qKynP#oS3$$a{p^{D+0oJQ71`1?OAn_m8)UGZmj3l*ZI)`V-a>MKGGFG< z&^jg#Ok%(hhm>hSrZ5;Qga4u(?^i>GiW_j9%_7M>j(^|Om$#{k+^*ULnEgzW_1gCICtAD^WpC`A z{9&DXkG#01Xo)U$OC(L5Y$DQ|Q4C6CjUKk1UkPj$nXH##J{c8e#K|&{mA*;b$r0E4 zUNo0jthwA(c&N1l=PEe8Rw_8cEl|-eya9z&H3#n`B$t#+aJ03RFMzrV@gowbe8v(c zIFM60^0&lCFO10NU4w@|61xiZ4CVXeaKjd;d?sv52XM*lS8XiVjgWpRB;&U_C0g+`6B5V&w|O6B*_q zsATxL!M}+$He)1eOWECce#eS@2n^xhlB4<_Nn?yCVEQWDs(r`|@2GqLe<#(|&P0U? z$7V5IgpWf09uIf_RazRwC?qEqRaHyL?iiS05UiGesJy%^>-C{{ypTBI&B0-iUYhk> zIk<5xpsuV@g|z(AZD+C-;A!fTG=df1=<%nxy(a(IS+U{ME4ZbDEBtcD_3V=icT6*_ z)>|J?>&6%nvHhZERBtjK+s4xnut*@>GAmA5m*OTp$!^CHTr}vM4n(X1Q*;{e-Rd2BCF-u@1ZGm z!S8hJ6L=Gl4T_SDa7Xx|-{4mxveJg=ctf`BJ*fy!yF6Dz&?w(Q_6B}WQVtNI!BVBC zKfX<>7vd6C96}XAQmF-Jd?1Q4eTfRB3q7hCh0f!(JkdWT5<{iAE#dKy*Jxq&3a1@~ z8C||Dn2mFNyrUV|<-)C^_y7@8c2Fz+2jrae9deBDu;U}tJ{^xAdxCD248(k;dCJ%o z`y3sADe>U%suxwwv~8A1+R$VB=Q?%U?4joI$um;aH+eCrBqpn- z%79D_7rb;R-;-9RTrwi9dPlg8&@tfWhhZ(Vx&1PQ+6(huX`;M9x~LrW~~#3{j0Bh2kDU$}@!fFQej4VGkJv?M4rU^x!RU zEwhu$!CA_iDjFjrJa`aocySDX16?~;+wgav;}Zut6Mg%C4>}8FL?8)Kgwc(Qlj{@#2Pt0?G`$h7P#M+qoXtlV@d}%c&OzO+QYKK`kyXaK{U(O^2DyIXCZlNQjt0^8~8JzNGrIxhj}}M z&~QZlbx%t;MJ(Vux;2tgNKGlAqphLq%pd}JG9uoVHUo?|hN{pLQ6Em%r*+7t^<);X zm~6=qChlNAVXNN*Sow->*4;}T;l;D1I-5T{Bif@4_}=>l`tK;qqDdt5zvisCKhMAH z#r}`)7VW?LZqfdmXQ%zo5bJ00{Xb9^YKrk0Nf|oIW*K@(=`o2Vndz}ZDyk{!u}PVx zzd--+_WC*U{~DH3{?GI64IB+@On&@9X>EUAo&L+G{L^dozaI4C3G#2wr~hseW@K&g zKWs{uHu-9Je!3;4pE>eBltKUXb^*hG8I&413)$J&{D4N%7PcloU6bn%jPxJyQL?g* z9g+YFFEDiE`8rW^laCNzQmi7CTnPfwyg3VDHRAl>h=In6jeaVOP@!-CP60j3+#vpL zEYmh_oP0{-gTe7Or`L6x)6w?77QVi~jD8lWN@3RHcm80iV%M1A!+Y6iHM)05iC64tb$X2lV_%Txk@0l^hZqi^%Z?#- zE;LE0uFx)R08_S-#(wC=dS&}vj6P4>5ZWjhthP=*Hht&TdLtKDR;rXEX4*z0h74FA zMCINqrh3Vq;s%3MC1YL`{WjIAPkVL#3rj^9Pj9Ss7>7duy!9H0vYF%>1jh)EPqvlr6h%R%CxDsk| z!BACz7E%j?bm=pH6Eaw{+suniuY7C9Ut~1cWfOX9KW9=H><&kQlinPV3h9R>3nJvK z4L9(DRM=x;R&d#a@oFY7mB|m8h4692U5eYfcw|QKwqRsshN(q^v$4$)HgPpAJDJ`I zkqjq(8Cd!K!+wCd=d@w%~e$=gdUgD&wj$LQ1r>-E=O@c ze+Z$x{>6(JA-fNVr)X;*)40Eym1TtUZI1Pwwx1hUi+G1Jlk~vCYeXMNYtr)1?qwyg zsX_e*$h?380O00ou?0R@7-Fc59o$UvyVs4cUbujHUA>sH!}L54>`e` zHUx#Q+Hn&Og#YVOuo*niy*GU3rH;%f``nk#NN5-xrZ34NeH$l`4@t);4(+0|Z#I>Y z)~Kzs#exIAaf--65L0UHT_SvV8O2WYeD>Mq^Y6L!Xu8%vnpofG@w!}R7M28?i1*T&zp3X4^OMCY6(Dg<-! zXmcGQrRgHXGYre7GfTJ)rhl|rs%abKT_Nt24_Q``XH{88NVPW+`x4ZdrMuO0iZ0g` z%p}y};~T5gbb9SeL8BSc`SO#ixC$@QhXxZ=B}L`tP}&k?1oSPS=4%{UOHe0<_XWln zwbl5cn(j-qK`)vGHY5B5C|QZd5)W7c@{bNVXqJ!!n$^ufc?N9C-BF2QK1(kv++h!>$QbAjq)_b$$PcJdV+F7hz0Hu@ zqj+}m0qn{t^tD3DfBb~0B36|Q`bs*xs|$i^G4uNUEBl4g;op-;Wl~iThgga?+dL7s zUP(8lMO?g{GcYpDS{NM!UA8Hco?#}eNEioRBHy4`mq!Pd-9@-97|k$hpEX>xoX+dY zDr$wfm^P&}Wu{!%?)U_(%Mn79$(ywvu*kJ9r4u|MyYLI_67U7%6Gd_vb##Nerf@>& z8W11z$$~xEZt$dPG}+*IZky+os5Ju2eRi;1=rUEeIn>t-AzC_IGM-IXWK3^6QNU+2pe=MBn4I*R@A%-iLDCOHTE-O^wo$sL_h{dcPl=^muAQb`_BRm};=cy{qSkui;`WSsj9%c^+bIDQ z0`_?KX0<-=o!t{u(Ln)v>%VGL z0pC=GB7*AQ?N7N{ut*a%MH-tdtNmNC+Yf$|KS)BW(gQJ*z$d{+{j?(e&hgTy^2|AR9vx1Xre2fagGv0YXWqtNkg*v%40v?BJBt|f9wX5 z{QTlCM}b-0{mV?IG>TW_BdviUKhtosrBqdfq&Frdz>cF~yK{P@(w{Vr7z2qKFwLhc zQuogKO@~YwyS9%+d-zD7mJG~@?EFJLSn!a&mhE5$_4xBl&6QHMzL?CdzEnC~C3$X@ zvY!{_GR06ep5;<#cKCSJ%srxX=+pn?ywDwtJ2{TV;0DKBO2t++B(tIO4)Wh`rD13P z4fE$#%zkd=UzOB74gi=-*CuID&Z3zI^-`4U^S?dHxK8fP*;fE|a(KYMgMUo`THIS1f!*6dOI2 zFjC3O=-AL`6=9pp;`CYPTdVX z8(*?V&%QoipuH0>WKlL8A*zTKckD!paN@~hh zmXzm~qZhMGVdQGd=AG8&20HW0RGV8X{$9LldFZYm zE?}`Q3i?xJRz43S?VFMmqRyvWaS#(~Lempg9nTM$EFDP(Gzx#$r)W&lpFKqcAoJh-AxEw$-bjW>`_+gEi z2w`99#UbFZGiQjS8kj~@PGqpsPX`T{YOj`CaEqTFag;$jY z8_{Wzz>HXx&G*Dx<5skhpETxIdhKH?DtY@b9l8$l?UkM#J-Snmts7bd7xayKTFJ(u zyAT&@6cAYcs{PBfpqZa%sxhJ5nSZBPji?Zlf&}#L?t)vC4X5VLp%~fz2Sx<*oN<7` z?ge=k<=X7r<~F7Tvp9#HB{!mA!QWBOf%EiSJ6KIF8QZNjg&x~-%e*tflL(ji_S^sO ztmib1rp09uon}RcsFi#k)oLs@$?vs(i>5k3YN%$T(5Or(TZ5JW9mA6mIMD08=749$ z!d+l*iu{Il7^Yu}H;lgw=En1sJpCKPSqTCHy4(f&NPelr31^*l%KHq^QE>z>Ks_bH zjbD?({~8Din7IvZeJ>8Ey=e;I?thpzD=zE5UHeO|neioJwG;IyLk?xOz(yO&0DTU~ z^#)xcs|s>Flgmp;SmYJ4g(|HMu3v7#;c*Aa8iF#UZo7CvDq4>8#qLJ|YdZ!AsH%^_7N1IQjCro

K7UpUK$>l@ zw`1S}(D?mUXu_C{wupRS-jiX~w=Uqqhf|Vb3Cm9L=T+w91Cu^ z*&Ty%sN?x*h~mJc4g~k{xD4ZmF%FXZNC;oVDwLZ_WvrnzY|{v8hc1nmx4^}Z;yriXsAf+Lp+OFLbR!&Ox?xABwl zu8w&|5pCxmu#$?Cv2_-Vghl2LZ6m7}VLEfR5o2Ou$x02uA-%QB2$c(c1rH3R9hesc zfpn#oqpbKuVsdfV#cv@5pV4^f_!WS+F>SV6N0JQ9E!T90EX((_{bSSFv9ld%I0&}9 zH&Jd4MEX1e0iqDtq~h?DBrxQX1iI0lIs<|kB$Yrh&cpeK0-^K%=FBsCBT46@h#yi!AyDq1V(#V}^;{{V*@T4WJ&U-NTq43w=|K>z8%pr_nC>%C(Wa_l78Ufib$r8Od)IIN=u>417 z`Hl{9A$mI5A(;+-Q&$F&h-@;NR>Z<2U;Y21>>Z;s@0V@SbkMQQj%_;~+qTuQ?c|AV zcWm3XZQHhP&R%QWarS%mJ!9R^&!_)*s(v+VR@I#QrAT}`17Y+l<`b-nvmDNW`De%y zrwTZ9EJrj1AFA>B`1jYDow}~*dfPs}IZMO3=a{Fy#IOILc8F0;JS4x(k-NSpbN@qM z`@aE_e}5{!$v3+qVs7u?sOV(y@1Os*Fgu`fCW9=G@F_#VQ%xf$hj0~wnnP0$hFI+@ zkQj~v#V>xn)u??YutKsX>pxKCl^p!C-o?+9;!Nug^ z{rP!|+KsP5%uF;ZCa5F;O^9TGac=M|=V z_H(PfkV1rz4jl?gJ(ArXMyWT4y(86d3`$iI4^l9`vLdZkzpznSd5Ikfrs8qcSy&>z zTIZgWZGXw0n9ibQxYWE@gI0(3#KA-dAdPcsL_|hg2@~C!VZDM}5;v_Nykfq!*@*Zf zE_wVgx82GMDryKO{U{D>vSzSc%B~|cjDQrt5BN=Ugpsf8H8f1lR4SGo#hCuXPL;QQ z#~b?C4MoepT3X`qdW2dNn& zo8)K}%Lpu>0tQei+{>*VGErz|qjbK#9 zvtd8rcHplw%YyQCKR{kyo6fgg!)6tHUYT(L>B7er5)41iG`j$qe*kSh$fY!PehLcD zWeKZHn<492B34*JUQh=CY1R~jT9Jt=k=jCU2=SL&&y5QI2uAG2?L8qd2U(^AW#{(x zThSy=C#>k+QMo^7caQcpU?Qn}j-`s?1vXuzG#j8(A+RUAY})F@=r&F(8nI&HspAy4 z4>(M>hI9c7?DCW8rw6|23?qQMSq?*Vx?v30U%luBo)B-k2mkL)Ljk5xUha3pK>EEj z@(;tH|M@xkuN?gsz;*bygizwYR!6=(Xgcg^>WlGtRYCozY<rFX2E>kaZo)O<^J7a`MX8Pf`gBd4vrtD|qKn&B)C&wp0O-x*@-|m*0egT=-t@%dD zgP2D+#WPptnc;_ugD6%zN}Z+X4=c61XNLb7L1gWd8;NHrBXwJ7s0ce#lWnnFUMTR& z1_R9Fin4!d17d4jpKcfh?MKRxxQk$@)*hradH2$3)nyXep5Z;B z?yX+-Bd=TqO2!11?MDtG0n(*T^!CIiF@ZQymqq1wPM_X$Iu9-P=^}v7npvvPBu!d$ z7K?@CsA8H38+zjA@{;{kG)#AHME>Ix<711_iQ@WWMObXyVO)a&^qE1GqpP47Q|_AG zP`(AD&r!V^MXQ^e+*n5~Lp9!B+#y3#f8J^5!iC@3Y@P`;FoUH{G*pj*q7MVV)29+j z>BC`a|1@U_v%%o9VH_HsSnM`jZ-&CDvbiqDg)tQEnV>b%Ptm)T|1?TrpIl)Y$LnG_ zzKi5j2Fx^K^PG1=*?GhK;$(UCF-tM~^=Z*+Wp{FSuy7iHt9#4n(sUuHK??@v+6*|10Csdnyg9hAsC5_OrSL;jVkLlf zHXIPukLqbhs~-*oa^gqgvtpgTk_7GypwH><53riYYL*M=Q@F-yEPLqQ&1Sc zZB%w}T~RO|#jFjMWcKMZccxm-SL)s_ig?OC?y_~gLFj{n8D$J_Kw%{r0oB8?@dWzn zB528d-wUBQzrrSSLq?fR!K%59Zv9J4yCQhhDGwhptpA5O5U?Hjqt>8nOD zi{)0CI|&Gu%zunGI*XFZh(ix)q${jT8wnnzbBMPYVJc4HX*9d^mz|21$=R$J$(y7V zo0dxdbX3N#=F$zjstTf*t8vL)2*{XH!+<2IJ1VVFa67|{?LP&P41h$2i2;?N~RA30LV`BsUcj zfO9#Pg1$t}7zpv#&)8`mis3~o+P(DxOMgz-V*(?wWaxi?R=NhtW}<#^Z?(BhSwyar zG|A#Q7wh4OfK<|DAcl9THc-W4*>J4nTevsD%dkj`U~wSUCh15?_N@uMdF^Kw+{agk zJ`im^wDqj`Ev)W3k3stasP`88-M0ZBs7;B6{-tSm3>I@_e-QfT?7|n0D~0RRqDb^G zyHb=is;IwuQ&ITzL4KsP@Z`b$d%B0Wuhioo1CWttW8yhsER1ZUZzA{F*K=wmi-sb#Ju+j z-l@In^IKnb{bQG}Ps>+Vu_W#grNKNGto+yjA)?>0?~X`4I3T@5G1)RqGUZuP^NJCq&^HykuYtMDD8qq+l8RcZNJsvN(10{ zQ1$XcGt}QH-U^WU!-wRR1d--{B$%vY{JLWIV%P4-KQuxxDeJaF#{eu&&r!3Qu{w}0f--8^H|KwE>)ORrcR+2Qf zb})DRcH>k0zWK8@{RX}NYvTF;E~phK{+F;MkIP$)T$93Ba2R2TvKc>`D??#mv9wg$ zd~|-`Qx5LwwsZ2hb*Rt4S9dsF%Cny5<1fscy~)d;0m2r$f=83<->c~!GNyb!U)PA; zq^!`@@)UaG)Ew(9V?5ZBq#c%dCWZrplmuM`o~TyHjAIMh0*#1{B>K4po-dx$Tk-Cq z=WZDkP5x2W&Os`N8KiYHRH#UY*n|nvd(U>yO=MFI-2BEp?x@=N<~CbLJBf6P)}vLS?xJXYJ2^<3KJUdrwKnJnTp{ zjIi|R=L7rn9b*D#Xxr4*R<3T5AuOS+#U8hNlfo&^9JO{VbH!v9^JbK=TCGR-5EWR@ zN8T-_I|&@A}(hKeL4_*eb!1G8p~&_Im8|wc>Cdir+gg90n1dw?QaXcx6Op_W1r=axRw>4;rM*UOpT#Eb9xU1IiWo@h?|5uP zka>-XW0Ikp@dIe;MN8B01a7+5V@h3WN{J=HJ*pe0uwQ3S&MyWFni47X32Q7SyCTNQ z+sR!_9IZa5!>f&V$`q!%H8ci!a|RMx5}5MA_kr+bhtQy{-^)(hCVa@I!^TV4RBi zAFa!Nsi3y37I5EK;0cqu|9MRj<^r&h1lF}u0KpKQD^5Y+LvFEwM zLU@@v4_Na#Axy6tn3P%sD^5P#<7F;sd$f4a7LBMk zGU^RZHBcxSA%kCx*eH&wgA?Qwazm8>9SCSz_!;MqY-QX<1@p$*T8lc?@`ikEqJ>#w zcG``^CoFMAhdEXT9qt47g0IZkaU)4R7wkGs^Ax}usqJ5HfDYAV$!=6?>J6+Ha1I<5 z|6=9soU4>E))tW$<#>F ziZ$6>KJf0bPfbx_)7-}tMINlc=}|H+$uX)mhC6-Hz+XZxsKd^b?RFB6et}O#+>Wmw9Ec9) z{q}XFWp{3@qmyK*Jvzpyqv57LIR;hPXKsrh{G?&dRjF%Zt5&m20Ll?OyfUYC3WRn{cgQ?^V~UAv+5 z&_m#&nIwffgX1*Z2#5^Kl4DbE#NrD&Hi4|7SPqZ}(>_+JMz=s|k77aEL}<=0Zfb)a z%F(*L3zCA<=xO)2U3B|pcTqDbBoFp>QyAEU(jMu8(jLA61-H!ucI804+B!$E^cQQa z)_ERrW3g!B9iLb3nn3dlkvD7KsY?sRvls3QC0qPi>o<)GHx%4Xb$5a3GBTJ(k@`e@ z$RUa^%S15^1oLEmA=sayrP5;9qtf!Z1*?e$ORVPsXpL{jL<6E)0sj&swP3}NPmR%FM?O>SQgN5XfHE< zo(4#Cv11(%Nnw_{_Ro}r6=gKd{k?NebJ~<~Kv0r(r0qe4n3LFx$5%x(BKvrz$m?LG zjLIc;hbj0FMdb9aH9Lpsof#yG$(0sG2%RL;d(n>;#jb!R_+dad+K;Ccw!|RY?uS(a zj~?=&M!4C(5LnlH6k%aYvz@7?xRa^2gml%vn&eKl$R_lJ+e|xsNfXzr#xuh(>`}9g zLHSyiFwK^-p!;p$yt7$F|3*IfO3Mlu9e>Dpx8O`37?fA`cj`C0B-m9uRhJjs^mRp# zWB;Aj6|G^1V6`jg7#7V9UFvnB4((nIwG?k%c7h`?0tS8J3Bn0t#pb#SA}N-|45$-j z$R>%7cc2ebAClXc(&0UtHX<>pd)akR3Kx_cK+n<}FhzmTx!8e9^u2e4%x{>T6pQ`6 zO182bh$-W5A3^wos0SV_TgPmF4WUP-+D25KjbC{y_6W_9I2_vNKwU(^qSdn&>^=*t z&uvp*@c8#2*paD!ZMCi3;K{Na;I4Q35zw$YrW5U@Kk~)&rw;G?d7Q&c9|x<Hg|CNMsxovmfth*|E*GHezPTWa^Hd^F4!B3sF;)? z(NaPyAhocu1jUe(!5Cy|dh|W2=!@fNmuNOzxi^tE_jAtzNJ0JR-avc_H|ve#KO}#S z#a(8secu|^Tx553d4r@3#6^MHbH)vmiBpn0X^29xEv!Vuh1n(Sr5I0V&`jA2;WS|Y zbf0e}X|)wA-Pf5gBZ>r4YX3Mav1kKY(ulAJ0Q*jB)YhviHK)w!TJsi3^dMa$L@^{` z_De`fF4;M87vM3Ph9SzCoCi$#Fsd38u!^0#*sPful^p5oI(xGU?yeYjn;Hq1!wzFk zG&2w}W3`AX4bxoVm03y>ts{KaDf!}b&7$(P4KAMP=vK5?1In^-YYNtx1f#}+2QK@h zeSeAI@E6Z8a?)>sZ`fbq9_snl6LCu6g>o)rO;ijp3|$vig+4t} zylEo7$SEW<_U+qgVcaVhk+4k+C9THI5V10qV*dOV6pPtAI$)QN{!JRBKh-D zk2^{j@bZ}yqW?<#VVuI_27*cI-V~sJiqQv&m07+10XF+#ZnIJdr8t`9s_EE;T2V;B z4UnQUH9EdX%zwh-5&wflY#ve!IWt0UE-My3?L#^Bh%kcgP1q{&26eXLn zTkjJ*w+(|_>Pq0v8{%nX$QZbf)tbJaLY$03;MO=Ic-uqYUmUCuXD>J>o6BCRF=xa% z3R4SK9#t1!K4I_d>tZgE>&+kZ?Q}1qo4&h%U$GfY058s%*=!kac{0Z+4Hwm!)pFLR zJ+5*OpgWUrm0FPI2ib4NPJ+Sk07j(`diti^i#kh&f}i>P4~|d?RFb#!JN)~D@)beox}bw?4VCf^y*`2{4`-@%SFTry2h z>9VBc9#JxEs1+0i2^LR@B1J`B9Ac=#FW=(?2;5;#U$0E0UNag_!jY$&2diQk_n)bT zl5Me_SUvqUjwCqmVcyb`igygB_4YUB*m$h5oeKv3uIF0sk}~es!{D>4r%PC*F~FN3owq5e0|YeUTSG#Vq%&Gk7uwW z0lDo#_wvflqHeRm*}l?}o;EILszBt|EW*zNPmq#?4A+&i0xx^?9obLyY4xx=Y9&^G;xYXYPxG)DOpPg!i_Ccl#3L}6xAAZzNhPK1XaC_~ z!A|mlo?Be*8Nn=a+FhgpOj@G7yYs(Qk(8&|h@_>w8Y^r&5nCqe0V60rRz?b5%J;GYeBqSAjo|K692GxD4` zRZyM2FdI+-jK2}WAZTZ()w_)V{n5tEb@>+JYluDozCb$fA4H)$bzg(Ux{*hXurjO^ zwAxc+UXu=&JV*E59}h3kzQPG4M)X8E*}#_&}w*KEgtX)cU{vm9b$atHa;s>| z+L6&cn8xUL*OSjx4YGjf6{Eq+Q3{!ZyhrL&^6Vz@jGbI%cAM9GkmFlamTbcQGvOlL zmJ?(FI)c86=JEs|*;?h~o)88>12nXlpMR4@yh%qdwFNpct;vMlc=;{FSo*apJ;p}! zAX~t;3tb~VuP|ZW;z$=IHf->F@Ml)&-&Bnb{iQyE#;GZ@C$PzEf6~q}4D>9jic@mTO5x76ulDz@+XAcm35!VSu zT*Gs>;f0b2TNpjU_BjHZ&S6Sqk6V1370+!eppV2H+FY!q*n=GHQ!9Rn6MjY!Jc77A zG7Y!lFp8?TIHN!LXO?gCnsYM-gQxsm=Ek**VmZu7vnuufD7K~GIxfxbsQ@qv2T zPa`tvHB$fFCyZl>3oYg?_wW)C>^_iDOc^B7klnTOoytQH18WkOk)L2BSD0r%xgRSW zQS9elF^?O=_@|58zKLK;(f77l-Zzu}4{fXed2saq!5k#UZAoDBqYQS{sn@j@Vtp|$ zG%gnZ$U|9@u#w1@11Sjl8ze^Co=)7yS(}=;68a3~g;NDe_X^}yJj;~s8xq9ahQ5_r zxAlTMnep*)w1e(TG%tWsjo3RR;yVGPEO4V{Zp?=a_0R#=V^ioQu4YL=BO4r0$$XTX zZfnw#_$V}sDAIDrezGQ+h?q24St0QNug_?{s-pI(^jg`#JRxM1YBV;a@@JQvH8*>> zIJvku74E0NlXkYe_624>znU0J@L<-c=G#F3k4A_)*;ky!C(^uZfj%WB3-*{*B$?9+ zDm$WFp=0(xnt6`vDQV3Jl5f&R(Mp};;q8d3I%Kn>Kx=^;uSVCw0L=gw53%Bp==8Sw zxtx=cs!^-_+i{2OK`Q;913+AXc_&Z5$@z3<)So0CU3;JAv=H?@Zpi~riQ{z-zLtVL z!oF<}@IgJp)Iyz1zVJ42!SPHSkjYNS4%ulVVIXdRuiZ@5Mx8LJS}J#qD^Zi_xQ@>DKDr-_e#>5h3dtje*NcwH_h;i{Sx7}dkdpuW z(yUCjckQsagv*QGMSi9u1`Z|V^}Wjf7B@q%j2DQXyd0nOyqg%m{CK_lAoKlJ7#8M} z%IvR?Vh$6aDWK2W!=i?*<77q&B8O&3?zP(Cs@kapc)&p7En?J;t-TX9abGT#H?TW? ztO5(lPKRuC7fs}zwcUKbRh=7E8wzTsa#Z{a`WR}?UZ%!HohN}d&xJ=JQhpO1PI#>X zHkb>pW04pU%Bj_mf~U}1F1=wxdBZu1790>3Dm44bQ#F=T4V3&HlOLsGH)+AK$cHk6 zia$=$kog?)07HCL*PI6}DRhpM^*%I*kHM<#1Se+AQ!!xyhcy6j7`iDX7Z-2i73_n# zas*?7LkxS-XSqv;YBa zW_n*32D(HTYQ0$feV_Fru1ZxW0g&iwqixPX3=9t4o)o|kOo79V$?$uh?#8Q8e>4e)V6;_(x&ViUVxma+i25qea;d-oK7ouuDsB^ab{ zu1qjQ%`n56VtxBE#0qAzb7lph`Eb-}TYpXB!H-}3Ykqyp`otprp7{VEuW*^IR2n$Fb99*nAtqT&oOFIf z@w*6>YvOGw@Ja?Pp1=whZqydzx@9X4n^2!n83C5{C?G@|E?&$?p*g68)kNvUTJ)I6 z1Q|(#UuP6pj78GUxq11m-GSszc+)X{C2eo-?8ud9sB=3(D47v?`JAa{V(IF zPZQ_0AY*9M97>Jf<o%#O_%Wq}8>YM=q0|tGY+hlXcpE=Z4Od z`NT7Hu2hnvRoqOw@g1f=bv`+nba{GwA$Ak0INlqI1k<9!x_!sL()h?hEWoWrdU3w` zZ%%)VR+Bc@_v!C#koM1p-3v_^L6)_Ktj4HE>aUh%2XZE@JFMOn)J~c`_7VWNb9c-N z2b|SZMR4Z@E7j&q&9(6H3yjEu6HV7{2!1t0lgizD;mZ9$r(r7W5G$ky@w(T_dFnOD z*p#+z$@pKE+>o@%eT(2-p_C}wbQ5s(%Sn_{$HDN@MB+Ev?t@3dPy`%TZ!z}AThZSu zN<1i$siJhXFdjV zP*y|V<`V8t=h#XTRUR~5`c`Z9^-`*BZf?WAehGdg)E2Je)hqFa!k{V(u+(hTf^Yq& zoruUh2(^3pe)2{bvt4&4Y9CY3js)PUHtd4rVG57}uFJL)D(JfSIo^{P=7liFXG zq5yqgof0V8paQcP!gy+;^pp-DA5pj=gbMN0eW=-eY+N8~y+G>t+x}oa!5r>tW$xhI zPQSv=pi;~653Gvf6~*JcQ%t1xOrH2l3Zy@8AoJ+wz@daW@m7?%LXkr!bw9GY@ns3e zSfuWF_gkWnesv?s3I`@}NgE2xwgs&rj?kH-FEy82=O8`+szN ziHch`vvS`zNfap14!&#i9H@wF7}yIPm=UB%(o(}F{wsZ(wA0nJ2aD^@B41>>o-_U6 zUqD~vdo48S8~FTb^+%#zcbQiiYoDKYcj&$#^;Smmb+Ljp(L=1Kt_J!;0s%1|JK}Wi z;={~oL!foo5n8=}rs6MmUW~R&;SIJO3TL4Ky?kh+b2rT9B1Jl4>#Uh-Bec z`Hsp<==#UEW6pGPhNk8H!!DUQR~#F9jEMI6T*OWfN^Ze&X(4nV$wa8QUJ>oTkruH# zm~O<`J7Wxseo@FqaZMl#Y(mrFW9AHM9Kb|XBMqaZ2a)DvJgYipkDD_VUF_PKd~dT7 z#02}bBfPn9a!X!O#83=lbJSK#E}K&yx-HI#T6ua)6o0{|={*HFusCkHzs|Fn&|C3H zBck1cmfcWVUN&i>X$YU^Sn6k2H;r3zuXbJFz)r5~3$d$tUj(l1?o={MM){kjgqXRO zc5R*#{;V7AQh|G|)jLM@wGAK&rm2~@{Pewv#06pHbKn#wL0P6F1!^qw9g&cW3Z=9} zj)POhOlwsh@eF=>z?#sIs*C-Nl(yU!#DaiaxhEs#iJqQ8w%(?+6lU02MYSeDkr!B- zPjMv+on6OLXgGnAtl(ao>|X2Y8*Hb}GRW5}-IzXnoo-d0!m4Vy$GS!XOLy>3_+UGs z2D|YcQx@M#M|}TDOetGi{9lGo9m-=0-^+nKE^*?$^uHkxZh}I{#UTQd;X!L+W@jm( zDg@N4+lUqI92o_rNk{3P>1gxAL=&O;x)ZT=q1mk0kLlE$WeWuY_$0`0jY-Kkt zP*|m3AF}Ubd=`<>(Xg0har*_@x2YH}bn0Wk*OZz3*e5;Zc;2uBdnl8?&XjupbkOeNZsNh6pvsq_ydmJI+*z**{I{0K)-;p1~k8cpJXL$^t!-`E}=*4G^-E8>H!LjTPxSx zcF+cS`ommfKMhNSbas^@YbTpH1*RFrBuATUR zt{oFWSk^$xU&kbFQ;MCX22RAN5F6eq9UfR$ut`Jw--p2YX)A*J69m^!oYfj2y7NYcH6&r+0~_sH^c^nzeN1AU4Ga7=FlR{S|Mm~MpzY0$Z+p2W(a={b-pR9EO1Rs zB%KY|@wLcAA@)KXi!d2_BxrkhDn`DT1=Dec}V!okd{$+wK z4E{n8R*xKyci1(CnNdhf$Dp2(Jpof0-0%-38X=Dd9PQgT+w%Lshx9+loPS~MOm%ZT zt%2B2iL_KU_ita%N>xjB!#71_3=3c}o zgeW~^U_ZTJQ2!PqXulQd=3b=XOQhwATK$y(9$#1jOQ4}4?~l#&nek)H(04f(Sr=s| zWv7Lu1=%WGk4FSw^;;!8&YPM)pQDCY9DhU`hMty1@sq1=Tj7bFsOOBZOFlpR`W>-J$-(kezWJj;`?x-v>ev{*8V z8p|KXJPV$HyQr1A(9LVrM47u-XpcrIyO`yWvx1pVYc&?154aneRpLqgx)EMvRaa#|9?Wwqs2+W8n5~79G z(}iCiLk;?enn}ew`HzhG+tu+Ru@T+K5juvZN)wY;x6HjvqD!&!)$$;1VAh~7fg0K| zEha#aN=Yv|3^~YFH}cc38ovVb%L|g@9W6fo(JtT6$fa?zf@Ct88e}m?i)b*Jgc{fl zExfdvw-BYDmH6>(4QMt#p0;FUIQqkhD}aH?a7)_%JtA~soqj{ppP_82yi9kaxuK>~ ze_)Zt>1?q=ZH*kF{1iq9sr*tVuy=u>Zev}!gEZx@O6-fjyu9X00gpIl-fS_pzjpqJ z1yqBmf9NF!jaF<+YxgH6oXBdK)sH(>VZ)1siyA$P<#KDt;8NT*l_0{xit~5j1P)FN zI8hhYKhQ)i z37^aP13B~u65?sg+_@2Kr^iWHN=U;EDSZ@2W2!5ALhGNWXnFBY%7W?1 z=HI9JzQ-pLKZDYTv<0-lt|6c-RwhxZ)mU2Os{bsX_i^@*fKUj8*aDO5pks=qn3Dv6 zwggpKLuyRCTVPwmw1r}B#AS}?X7b837UlXwp~E2|PJw2SGVueL7){Y&z!jL!XN=0i zU^Eig`S2`{+gU$68aRdWx?BZ{sU_f=8sn~>s~M?GU~`fH5kCc; z8ICp+INM3(3{#k32RZdv6b9MQYdZXNuk7ed8;G?S2nT+NZBG=Tar^KFl2SvhW$bGW#kdWL-I)s_IqVnCDDM9fm8g;P;8 z7t4yZn3^*NQfx7SwmkzP$=fwdC}bafQSEF@pd&P8@H#`swGy_rz;Z?Ty5mkS%>m#% zp_!m9e<()sfKiY(nF<1zBz&&`ZlJf6QLvLhl`_``%RW&{+O>Xhp;lwSsyRqGf=RWd zpftiR`={2(siiPAS|p}@q=NhVc0ELprt%=fMXO3B)4ryC2LT(o=sLM7hJC!}T1@)E zA3^J$3&1*M6Xq>03FX`R&w*NkrZE?FwU+Muut;>qNhj@bX17ZJxnOlPSZ=Zeiz~T_ zOu#yc3t6ONHB;?|r4w+pI)~KGN;HOGC)txxiUN8#mexj+W(cz%9a4sx|IRG=}ia zuEBuba3AHsV2feqw-3MvuL`I+2|`Ud4~7ZkN=JZ;L20|Oxna5vx1qbIh#k2O4$RQF zo`tL()zxaqibg^GbB+BS5#U{@K;WWQj~GcB1zb}zJkPwH|5hZ9iH2308!>_;%msji zJHSL~s)YHBR=Koa1mLEOHos*`gp=s8KA-C zu0aE+W!#iJ*0xqKm3A`fUGy#O+X+5W36myS>Uh2!R*s$aCU^`K&KKLCCDkejX2p=5 z%o7-fl03x`gaSNyr?3_JLv?2RLS3F*8ub>Jd@^Cc17)v8vYEK4aqo?OS@W9mt%ITJ z9=S2%R8M){CugT@k~~0x`}Vl!svYqX=E)c_oU6o}#Hb^%G1l3BudxA{F*tbjG;W_>=xV73pKY53v%>I)@D36I_@&p$h|Aw zonQS`07z_F#@T-%@-Tb|)7;;anoD_WH>9ewFy(ZcEOM$#Y)8>qi7rCnsH9GO-_7zF zu*C87{Df1P4TEOsnzZ@H%&lvV(3V@;Q!%+OYRp`g05PjY^gL$^$-t0Y>H*CDDs?FZly*oZ&dxvsxaUWF!{em4{A>n@vpXg$dwvt@_rgmHF z-MER`ABa8R-t_H*kv>}CzOpz;!>p^^9ztHMsHL|SRnS<-y5Z*r(_}c4=fXF`l^-i}>e7v!qs_jv zqvWhX^F=2sDNWA9c@P0?lUlr6ecrTKM%pNQ^?*Lq?p-0~?_j50xV%^(+H>sMul#Tw zeciF*1=?a7cI(}352%>LO96pD+?9!fNyl^9v3^v&Y4L)mNGK0FN43&Xf8jUlxW1Bw zyiu2;qW-aGNhs=zbuoxnxiwZ3{PFZM#Kw)9H@(hgX23h(`Wm~m4&TvoZoYp{plb^> z_#?vXcxd>r7K+1HKJvhed>gtK`TAbJUazUWQY6T~t2af%#<+Veyr%7-#*A#@&*;@g58{i|E%6yC_InGXCOd{L0;$)z#?n7M`re zh!kO{6=>7I?*}czyF7_frt#)s1CFJ_XE&VrDA?Dp3XbvF{qsEJgb&OLSNz_5g?HpK z9)8rsr4JN!Af3G9!#Qn(6zaUDqLN(g2g8*M)Djap?WMK9NKlkC)E2|-g|#-rp%!Gz zAHd%`iq|81efi93m3yTBw3g0j#;Yb2X{mhRAI?&KDmbGqou(2xiRNb^sV}%%Wu0?< z?($L>(#BO*)^)rSgyNRni$i`R4v;GhlCZ8$@e^ROX(p=2_v6Y!%^As zu022)fHdv_-~Yu_H6WVPLpHQx!W%^6j)cBhS`O3QBW#x(eX54d&I22op(N59b*&$v zFiSRY6rOc^(dgSV1>a7-5C;(5S5MvKcM2Jm-LD9TGqDpP097%52V+0>Xqq!! zq4e3vj53SE6i8J`XcQB|MZPP8j;PAOnpGnllH6#Ku~vS42xP*Nz@~y%db7Xi8s09P z1)e%8ys6&M8D=Dt6&t`iKG_4X=!kgRQoh%Z`dc&mlOUqXk-k`jKv9@(a^2-Upw>?< zt5*^DV~6Zedbec4NVl($2T{&b)zA@b#dUyd>`2JC0=xa_fIm8{5um zr-!ApXZhC8@=vC2WyxO|!@0Km)h8ep*`^he92$@YwP>VcdoS5OC^s38e#7RPsg4j+ zbVGG}WRSET&ZfrcR(x~k8n1rTP%CnfUNKUonD$P?FtNFF#cn!wEIab-;jU=B1dHK@ z(;(yAQJ`O$sMn>h;pf^8{JISW%d+@v6@CnXh9n5TXGC}?FI9i-D0OMaIg&mAg=0Kn zNJ7oz5*ReJukD55fUsMuaP+H4tDN&V9zfqF@ zr=#ecUk9wu{0;!+gl;3Bw=Vn^)z$ahVhhw)io!na&9}LmWurLb0zubxK=UEnU*{5P z+SP}&*(iBKSO4{alBHaY^)5Q=mZ+2OwIooJ7*Q5XJ+2|q`9#f?6myq!&oz?klihLq z4C)$XP!BNS0G_Z1&TM>?Jk{S~{F3n83ioli=IO6f%wkvCl(RFFw~j0tb{GvXTx>*sB0McY0s&SNvj4+^h`9nJ_wM>F!Uc>X}9PifQekn0sKI2SAJP!a4h z5cyGTuCj3ZBM^&{dRelIlT^9zcfaAuL5Y~bl!ppSf`wZbK$z#6U~rdclk``e+!qhe z6Qspo*%<)eu6?C;Bp<^VuW6JI|Ncvyn+LlSl;Mp22Bl7ARQ0Xc24%29(ZrdsIPw&-=yHQ7_Vle|5h>AST0 zUGX2Zk34vp?U~IHT|;$U86T+UUHl_NE4m|}>E~6q``7hccCaT^#y+?wD##Q%HwPd8 zV3x4L4|qqu`B$4(LXqDJngNy-{&@aFBvVsywt@X^}iH7P%>bR?ciC$I^U-4Foa`YKI^qDyGK7k%E%c_P=yzAi`YnxGA%DeNd++j3*h^ z=rn>oBd0|~lZ<6YvmkKY*ZJlJ;Im0tqgWu&E92eqt;+NYdxx`eS(4Hw_Jb5|yVvBg z*tbdY^!AN;luEyN4VRhS@-_DC{({ziH{&Z}iGElSV~qvT>L-8G%+yEL zX#MFOhj{InyKG=mvW-<1B@c-}x$vA(nU?>S>0*eN#!SLzQ)Ex7fvQ)S4D<8|I#N$3 zT5Ei`Z?cxBODHX8(Xp73v`IsAYC@9b;t}z0wxVuQSY1J^GRwDPN@qbM-ZF48T$GZ< z8WU+;Pqo?{ghI-KZ-i*ydXu`Ep0Xw^McH_KE9J0S7G;x8Fe`DVG?j3Pv=0YzJ}yZR z%2=oqHiUjvuk0~Ca>Kol4CFi0_xQT~;_F?=u+!kIDl-9g`#ZNZ9HCy17Ga1v^Jv9# z{T4Kb1-AzUxq*MutfOWWZgD*HnFfyYg0&e9f(5tZ>krPF6{VikNeHoc{linPPt#Si z&*g>(c54V8rT_AX!J&bNm-!umPvOR}vDai#`CX___J#=zeB*{4<&2WpaDncZsOkp* zsg<%@@rbrMkR_ux9?LsQxzoBa1s%$BBn6vk#{&&zUwcfzeCBJUwFYSF$08qDsB;gWQN*g!p8pxjofWbqNSZOEKOaTx@+* zwdt5*Q47@EOZ~EZL9s?1o?A%9TJT=Ob_13yyugvPg*e&ZU(r6^k4=2+D-@n=Hv5vu zSXG|hM(>h9^zn=eQ=$6`JO&70&2|%V5Lsx>)(%#;pcOfu>*nk_3HB_BNaH$`jM<^S zcSftDU1?nL;jy)+sfonQN}(}gUW?d_ikr*3=^{G)=tjBtEPe>TO|0ddVB zTklrSHiW+!#26frPXQQ(YN8DG$PZo?(po(QUCCf_OJC`pw*uey00%gmH!`WJkrKXj2!#6?`T25mTu9OJp2L8z3! z=arrL$ZqxuE{%yV)14Kd>k}j7pxZ6#$Dz8$@WV5p8kTqN<-7W)Q7Gt2{KoOPK_tZ| zf2WG~O5@{qPI+W<4f_;reuFVdO^5`ADC1!JQE|N`s3cq@(0WB!n0uh@*c{=LAd;~} zyGK@hbF-Oo+!nN)@i*O(`@FA#u?o=~e{`4O#5}z&=UkU*50fOrzi11D^&FOqe>wii z?*k+2|EcUs;Gx{!@KBT~>PAwLrIDT7Th=Utu?~?np@t^gFs?zgX=D${RwOY^WGh-+ z+#4$066ISh8eYW#FXWp~S`<*%O^ZuItL1Tyqt8#tZ zY120E;^VG`!lZn&3sPd$RkdHpU#|w+bYV)pJC|SH9g%|5IkxVTQcBA4CL0}$&}ef@ zW^Vtj%M;;_1xxP9x#ex17&4N*{ksO*_4O}xYu(p*JkL#yr}@7b)t5X?%CY<+s5_MJ zuiqt+N_;A(_)%lumoyRFixWa-M7qK_9s6<1X?JDa9fP!+_6u~~M$5L=ipB=7(j#f< zZ34J%=bs549%~_mA(|={uZNs_0?o7;-LBP(ZRnkd{-^|2|=4vUTmtByHL8 zEph`(LSEzQj68a+`d$V<45J7cyv^#|^|%fD#si1Nx!4NW*`l*{->HEWNh6-|g>-=r zXmQ|-i}Ku$ndUeHQ^&ieT!Lf}vf6GaqW9$DJ2NWrqwPY%%4nip$@vK$nRp*_C-v<| zuKz~ZyN&<%!NS26&x?jhy+@awJipMQ-8(X4#Ae5??U<1QMt1l9R=w9fAnEF}NYu$2 z>6}Vkc zIb*A?G*z8^IvibmBKn_u^5&T_1oey0gZS2~obf(#xk=erZGTEdQnt3DMGM+0oPwss zj5zXD;(oWhB_T@~Ig#9@v)AKtXu3>Inmgf@A|-lD-1U>cNyl3h?ADD9)GG4}zUGPk zZzaXe!~Kf?<~@$G?Uql3t8jy9{2!doq4=J}j9ktTxss{p6!9UdjyDERlA*xZ!=Q)KDs5O)phz>Vq3BNGoM(H|=1*Q4$^2fTZw z(%nq1P|5Rt81}SYJpEEzMPl5VJsV5&4e)ZWKDyoZ>1EwpkHx-AQVQc8%JMz;{H~p{=FXV>jIxvm4X*qv52e?Y-f%DJ zxEA165GikEASQ^fH6K#d!Tpu2HP{sFs%E=e$gYd$aj$+xue6N+Wc(rAz~wUsk2`(b z8Kvmyz%bKQxpP}~baG-rwYcYCvkHOi zlkR<=>ZBTU*8RF_d#Bl@zZsRIhx<%~Z@Z=ik z>adw3!DK(8R|q$vy{FTxw%#xliD~6qXmY^7_9kthVPTF~Xy1CfBqbU~?1QmxmU=+k z(ggxvEuA;0e&+ci-zQR{-f7aO{O(Pz_OsEjLh_K>MbvoZ4nxtk5u{g@nPv)cgW_R} z9}EA4K4@z0?7ue}Z(o~R(X&FjejUI2g~08PH1E4w>9o{)S(?1>Z0XMvTb|;&EuyOE zGvWNpYX)Nv<8|a^;1>bh#&znEcl-r!T#pn= z4$?Yudha6F%4b>*8@=BdtXXY4N+`U4Dmx$}>HeVJk-QdTG@t!tVT#0(LeV0gvqyyw z2sEp^9eY0N`u10Tm4n8No&A=)IeEC|gnmEXoNSzu!1<4R<%-9kY_8~5Ej?zRegMn78wuMs#;i&eUA0Zk_RXQ3b&TT} z;SCI=7-FUB@*&;8|n>(_g^HGf3@QODE3LpmX~ELnymQm{Sx9xrKS zK29p~?v@R$0=v6Dr5aW>-!{+h@?Q58|Kz8{{W`%J+lDAdb&M5VHrX_mDY;1-JLnf)ezmPau$)1;=`-FU=-r-83tX=C`S#}GZufju zQ>sXNT0Ny=k@nc%cFnvA_i4SC)?_ORXHq8B4D%el1uPX`c~uG#S1M7C+*MMqLw78E zhY2dI8@+N^qrMI1+;TUda(vGqGSRyU{Fnm`aqrr7bz42c5xsOO-~oZpkzorD1g}Y<6rk&3>PsSGy}W?MtqFky@A(X# zIuNZK0cK?^=;PUAu>j0#HtjbHCV*6?jzA&OoE$*Jlga*}LF`SF?WLhv1O|zqC<>*> zYB;#lsYKx0&kH@BFpW8n*yDcc6?;_zaJs<-jPSkCsSX-!aV=P5kUgF@Nu<{a%#K*F z134Q{9|YX7X(v$62_cY3^G%t~rD>Q0z@)1|zs)vjJ6Jq9;7#Ki`w+eS**En?7;n&7 zu==V3T&eFboN3ZiMx3D8qYc;VjFUk_H-WWCau(VFXSQf~viH0L$gwD$UfFHqNcgN`x}M+YQ6RnN<+@t>JUp#)9YOkqst-Ga?{FsDpEeX0(5v{0J~SEbWiL zXC2}M4?UH@u&|;%0y`eb33ldo4~z-x8zY!oVmV=c+f$m?RfDC35mdQ2E>Pze7KWP- z>!Bh<&57I+O_^s}9Tg^k)h7{xx@0a0IA~GAOt2yy!X%Q$1rt~LbTB6@Du!_0%HV>N zlf)QI1&gvERKwso23mJ!Ou6ZS#zCS5W`gxE5T>C#E|{i<1D35C222I33?Njaz`On7 zi<+VWFP6D{e-{yiN#M|Jgk<44u1TiMI78S5W`Sdb5f+{zu34s{CfWN7a3Cf^@L%!& zN$?|!!9j2c)j$~+R6n#891w-z8(!oBpL2K=+%a$r2|~8-(vQj5_XT`<0Ksf;oP+tz z9CObS!0m)Tgg`K#xBM8B(|Z)Wb&DYL{WTYv`;A=q6~Nnx2+!lTIXtj8J7dZE!P_{z z#f8w6F}^!?^KE#+ZDv+xd5O&3EmomZzsv?>E-~ygGum45fk!SBN&|eo1rKw^?aZJ4 E2O(~oYXATM diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 18330fc..f193d72 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..b9bb139 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,81 +15,114 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd3..24c62d5 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,19 +13,22 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% From ace723e966ab4ca016e644023227472d2023ec20 Mon Sep 17 00:00:00 2001 From: Michal Bazanski Date: Thu, 7 May 2026 09:36:43 +0200 Subject: [PATCH 11/11] Upgrade Java target from 8 to 17 Add Java 25 to the CI build matrix. --- .github/workflows/gradle.yml | 2 +- build.gradle.kts | 4 ++-- configuration-test/build.gradle.kts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 033a848..e414393 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '17', '21' ] + java: [ '17', '21', '25' ] steps: - uses: actions/checkout@v4 diff --git a/build.gradle.kts b/build.gradle.kts index 2a94c58..3c88d76 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,8 +10,8 @@ repositories { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 withSourcesJar() withJavadocJar() } diff --git a/configuration-test/build.gradle.kts b/configuration-test/build.gradle.kts index 96c97a9..50208f0 100644 --- a/configuration-test/build.gradle.kts +++ b/configuration-test/build.gradle.kts @@ -6,8 +6,8 @@ group = "de.cronn" version = "0.0.1-SNAPSHOT" java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 withSourcesJar() withJavadocJar() }