From 4aafbbfc72e6a51135212467cd526771a37d94a8 Mon Sep 17 00:00:00 2001 From: Florian Kleedorfer Date: Wed, 8 Jul 2026 10:59:58 +0200 Subject: [PATCH 1/3] Replace de.atextor:turtle-formatter with cool.rdf:cool-rdf-formatter --- .../spotless/rdf/RdfFormatterStep.java | 4 +-- .../spotless/rdf/ReflectionHelper.java | 33 +++++++++---------- plugin-maven/README.md | 14 ++++---- .../shacl-shacl.ttl | 0 .../shacl.ttl | 0 .../shacl-shacl.ttl | 0 .../shacl.ttl | 0 .../spotless/rdf/RdfFormatterTest.java | 12 +++---- 8 files changed, 30 insertions(+), 33 deletions(-) rename testlib/src/main/resources/rdf/ttl/expected/{v1.2.12-default => v2.0.0-default}/shacl-shacl.ttl (100%) rename testlib/src/main/resources/rdf/ttl/expected/{v1.2.12-default => v2.0.0-default}/shacl.ttl (100%) rename testlib/src/main/resources/rdf/ttl/expected/{v1.2.12-style01 => v2.0.0-style01}/shacl-shacl.ttl (100%) rename testlib/src/main/resources/rdf/ttl/expected/{v1.2.12-style01 => v2.0.0-style01}/shacl.ttl (100%) diff --git a/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java index dce13ea0f5..b04d6140b4 100644 --- a/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java @@ -27,11 +27,11 @@ import com.diffplug.spotless.Provisioner; public class RdfFormatterStep implements Serializable { - public static final String LATEST_TURTLE_FORMATTER_VERSION = "1.2.13"; + public static final String LATEST_TURTLE_FORMATTER_VERSION = "2.0.0"; @Serial private static final long serialVersionUID = 1L; - private static final String TURTLE_FORMATTER_COORDINATES = "de.atextor:turtle-formatter"; + private static final String TURTLE_FORMATTER_COORDINATES = "cool.rdf:cool-rdf-formatter"; private final JarState.Promised jarState; private final Map turtleFormatterStyle; diff --git a/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java b/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java index 061062434f..451e3a534b 100644 --- a/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java @@ -18,7 +18,6 @@ import java.io.File; import java.io.StringWriter; import java.lang.invoke.MethodHandles; -import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -64,7 +63,8 @@ class ReflectionHelper { private final Class TurtleFormatFormattingStyleClass; private final Class TurtleFormatFormattingStyleBuilderClass; private final Class TurtleFormatFormatterClass; - private final Class TurtleFormatKnownPrefix; + private final Class CoolRdfPrefixesClass; + private final Class CoolRdfPrefixClass; private final Method graphStream; private final Method graphFindTriple; @@ -96,12 +96,13 @@ public ReflectionHelper(RdfFormatterStep.State state) this.JenaModelFactoryClass = classLoader.loadClass("org.apache.jena.rdf.model.ModelFactory"); this.JenaLangClass = classLoader.loadClass("org.apache.jena.riot.Lang"); this.JenaRDFFormatClass = classLoader.loadClass("org.apache.jena.riot.RDFFormat"); - this.TurtleFormatFormatterClass = classLoader.loadClass("de.atextor.turtle.formatter.TurtleFormatter"); - this.TurtleFormatFormattingStyleClass = classLoader.loadClass("de.atextor.turtle.formatter.FormattingStyle"); + this.TurtleFormatFormatterClass = classLoader.loadClass("cool.rdf.formatter.TurtleFormatter"); + this.TurtleFormatFormattingStyleClass = classLoader.loadClass("cool.rdf.formatter.FormattingStyle"); Class[] innerClasses = TurtleFormatFormattingStyleClass.getDeclaredClasses(); this.TurtleFormatFormattingStyleBuilderClass = Arrays.stream(innerClasses) .filter(c -> "FormattingStyleBuilder".equals(c.getSimpleName())).findFirst().orElseThrow(); - this.TurtleFormatKnownPrefix = Arrays.stream(innerClasses).filter(c -> "KnownPrefix".equals(c.getSimpleName())).findFirst().orElseThrow(); + this.CoolRdfPrefixesClass = classLoader.loadClass("cool.rdf.core.Prefixes"); + this.CoolRdfPrefixClass = classLoader.loadClass("cool.rdf.core.model.RdfPrefix"); this.getSubject = JenaStatementClass.getMethod("getSubject"); this.getPredicate = JenaStatementClass.getMethod("getPredicate"); this.getObject = JenaStatementClass.getMethod("getObject"); @@ -391,7 +392,7 @@ private Object instantiate(Type type, String stringRepresentation) .invoke(this.jenaModelInstance, namespace, localname); } } - if (type.equals(TurtleFormatKnownPrefix)) { + if (type.equals(CoolRdfPrefixClass)) { return getKnownPrefix(stringRepresentation); } throw new IllegalArgumentException("Cannot instantiate class %s from string representation %s".formatted(type, stringRepresentation)); @@ -404,7 +405,7 @@ private String tryToMakeUri(String stringRepresentation) //could be a known prefix String prefix = stringRepresentation.substring(0, colonIndex); Object knownPrefix = getKnownPrefix(prefix); - String base = this.TurtleFormatKnownPrefix.getMethod("iri").invoke(knownPrefix).toString(); + String base = this.CoolRdfPrefixClass.getMethod("uri").invoke(knownPrefix).toString(); return base + stringRepresentation.substring(colonIndex + 1); } // try to parse a URI - throws an IllegalArgumentException if it is not a URI @@ -414,20 +415,16 @@ private String tryToMakeUri(String stringRepresentation) private Object getKnownPrefix(String stringRepresentation) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { - Field[] fields = TurtleFormatFormattingStyleClass.getDeclaredFields(); List options = new ArrayList<>(); - for (Field field : fields) { - if (field.getType().equals(TurtleFormatKnownPrefix)) { - Object knownPrefix = field.get(TurtleFormatFormattingStyleClass); - String prefix = (String) TurtleFormatKnownPrefix.getMethod("prefix").invoke(knownPrefix); - options.add(prefix); - if (stringRepresentation.equals(prefix)) { - return knownPrefix; - } + for (Object knownPrefix : CoolRdfPrefixesClass.getEnumConstants()) { + String prefix = (String) CoolRdfPrefixClass.getMethod("prefix").invoke(knownPrefix); + options.add(prefix); + if (stringRepresentation.equals(prefix)) { + return knownPrefix; } } - throw new IllegalArgumentException("Unable to find FormattingStyle.KnownPrefix for prefix '%s'. Options are: %s".formatted(stringRepresentation, options.stream().collect( - Collectors.joining(",\n\t", "\n\t", "\n")))); + throw new IllegalArgumentException("Unable to find a known Cool RDF prefix for '%s'. Options are: %s".formatted(stringRepresentation, options.stream().collect( + Collectors.joining(", ")))); } private static String[] split(String parameterValueAsString) { diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 1d0bf6d25f..73bd79ec12 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -16,7 +16,7 @@ output = [ output = prefixDelimiterReplace(input, 'https://{{org}}.github.io/{{name}}/javadoc/spotless-plugin-maven/', '/', versionLast) --> -Spotless is a general-purpose formatting plugin used by [6,000 projects on GitHub (Jan 2023)](https://github.com/search?l=Maven+POM&q=spotless&type=Code). It is completely à la carte, but also includes powerful "batteries-included" if you opt-in. Plugin requires a version of Maven higher or equal to 3.1.0. +Spotless is a general-purpose formatting plugin used by [6,000 projects on GitHub (Jan 2023)](https://github.com/search?l=Maven+POM&q=spotless&type=Code). It is completely � la carte, but also includes powerful "batteries-included" if you opt-in. Plugin requires a version of Maven higher or equal to 3.1.0. To people who use your build, it looks like this: @@ -1320,16 +1320,16 @@ List of generic configuration `parameters (type/default)` the build fails for any of them. You can ignore warnings using this parameter. They will still be logged in the plugin's output. * `verify (boolean/true)`: If `true`, the content before and after formatting is parsed to an RDF model and compared for isomorphicity. -* `turtleFormatterVersion (string|RdfFormatterStep.LATEST_TURTLE_FORMATTER_VERSION)`: the version of turtle-formatter to use (see below). +* `turtleFormatterVersion (string|RdfFormatterStep.LATEST_TURTLE_FORMATTER_VERSION)`: the version of Cool RDF Formatter to use (see below). ### Supported RDF formats: only TTL (at the moment) -Formatting TTL is done using [turtle-formatter](https://github.com/atextor/turtle-formatter), -which is highly configurable (have a look at the [Style Documentation](https://github.com/atextor/turtle-formatter?tab=readme-ov-file#customizing-the-style)) +Formatting TTL is done using [Cool RDF Formatter](https://github.com/cool-rdf/cool-rdf/tree/main/cool-rdf-formatter), +which is highly configurable (have a look at the [Style Documentation](https://github.com/cool-rdf/cool-rdf/tree/main/cool-rdf-formatter)) and will handle blank nodes the way you'd hope. The style options can be configured via spotless. Wherever the style wants a URI (for example, for the `predicateOrder`, you can -use the abbreviated form if it is a `FormattingStyle.KnownPrefix` (currently `rdf`, `rdfs`, `xsd`, `owl`, `dcterms`) +use the abbreviated form if it is a known `RdfPrefix` from Cool RDF's `Prefixes` enum Error messages will give you hints. To configure the TTL formatting style, pass the configuration parameters under `` ### Examples @@ -1354,7 +1354,7 @@ Configuring some generic and TTL options: false false - 1.2.13 + 2.0.0 RIGHT true @@ -1366,7 +1366,7 @@ Configuring some generic and TTL options: ### Libraries and versions RDF parsing is done via [Apache Jena](https://jena.apache.org/) in the version that -[turtle-formatter](https://github.com/atextor/turtle-formatter) depends on (not necessarily the latest). +[Cool RDF Formatter](https://github.com/cool-rdf/cool-rdf/tree/main/cool-rdf-formatter) depends on (not necessarily the latest). ## Protobuf diff --git a/testlib/src/main/resources/rdf/ttl/expected/v1.2.12-default/shacl-shacl.ttl b/testlib/src/main/resources/rdf/ttl/expected/v2.0.0-default/shacl-shacl.ttl similarity index 100% rename from testlib/src/main/resources/rdf/ttl/expected/v1.2.12-default/shacl-shacl.ttl rename to testlib/src/main/resources/rdf/ttl/expected/v2.0.0-default/shacl-shacl.ttl diff --git a/testlib/src/main/resources/rdf/ttl/expected/v1.2.12-default/shacl.ttl b/testlib/src/main/resources/rdf/ttl/expected/v2.0.0-default/shacl.ttl similarity index 100% rename from testlib/src/main/resources/rdf/ttl/expected/v1.2.12-default/shacl.ttl rename to testlib/src/main/resources/rdf/ttl/expected/v2.0.0-default/shacl.ttl diff --git a/testlib/src/main/resources/rdf/ttl/expected/v1.2.12-style01/shacl-shacl.ttl b/testlib/src/main/resources/rdf/ttl/expected/v2.0.0-style01/shacl-shacl.ttl similarity index 100% rename from testlib/src/main/resources/rdf/ttl/expected/v1.2.12-style01/shacl-shacl.ttl rename to testlib/src/main/resources/rdf/ttl/expected/v2.0.0-style01/shacl-shacl.ttl diff --git a/testlib/src/main/resources/rdf/ttl/expected/v1.2.12-style01/shacl.ttl b/testlib/src/main/resources/rdf/ttl/expected/v2.0.0-style01/shacl.ttl similarity index 100% rename from testlib/src/main/resources/rdf/ttl/expected/v1.2.12-style01/shacl.ttl rename to testlib/src/main/resources/rdf/ttl/expected/v2.0.0-style01/shacl.ttl diff --git a/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java b/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java index 95ca530caa..4a52313309 100644 --- a/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java @@ -49,17 +49,17 @@ private static FormatterStep forTurtleFormatterVersionAndStyle(String version, M public RdfFormatterTest() {} @Test - void testTurtleFormatter_1_2_12_DefaultStyle() throws IOException, ClassNotFoundException { + void testCoolRdfFormatter_2_0_0_DefaultStyle() throws IOException, ClassNotFoundException { String inputDir = "/rdf/ttl/input/"; - String expectedOutputDir = "/rdf/ttl/expected/v1.2.12-default/"; - testBeforeAfterFolders(inputDir, expectedOutputDir, StepHarness.forStep(forTurtleFormatterVersion("1.2.12"))); + String expectedOutputDir = "/rdf/ttl/expected/v2.0.0-default/"; + testBeforeAfterFolders(inputDir, expectedOutputDir, StepHarness.forStep(forTurtleFormatterVersion("2.0.0"))); } @Test - void testTurtleFormatter_1_2_12_style01() throws IOException, ClassNotFoundException { + void testCoolRdfFormatter_2_0_0_style01() throws IOException, ClassNotFoundException { String inputDir = "/rdf/ttl/input/"; - String expectedOutputDir = "/rdf/ttl/expected/v1.2.12-style01/"; - testBeforeAfterFolders(inputDir, expectedOutputDir, StepHarness.forStep(forTurtleFormatterVersionAndStyle("1.2.12", style01()))); + String expectedOutputDir = "/rdf/ttl/expected/v2.0.0-style01/"; + testBeforeAfterFolders(inputDir, expectedOutputDir, StepHarness.forStep(forTurtleFormatterVersionAndStyle("2.0.0", style01()))); } private static @NotNull Map defaultStyle() { From 23de600dc2bb9a5c3bb3e4313e6f16e310ed728c Mon Sep 17 00:00:00 2001 From: Florian Kleedorfer Date: Wed, 8 Jul 2026 15:48:52 +0200 Subject: [PATCH 2/3] Update README and CHANGES.md, add a test --- CHANGES.md | 3 ++ plugin-maven/README.md | 9 +++-- .../spotless/rdf/RdfFormatterTest.java | 38 ++++++++++++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5b0e8c042a..cf2ac0ecdb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] +## Changes +- Replace RDF formatter library `de.atextor:turtle-formatter` (discontinued) with `cool.rdf:cool-rdf-formatter` (its new coordinates). + ## [4.8.0] - 2026-06-29 ### Added - Add support for custom string format for license header copyright year via `yearStringFormat()`. ([#2965](https://github.com/diffplug/spotless/pull/2965)) diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 73bd79ec12..7e565d5954 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -1328,9 +1328,11 @@ Formatting TTL is done using [Cool RDF Formatter](https://github.com/cool-rdf/co which is highly configurable (have a look at the [Style Documentation](https://github.com/cool-rdf/cool-rdf/tree/main/cool-rdf-formatter)) and will handle blank nodes the way you'd hope. -The style options can be configured via spotless. Wherever the style wants a URI (for example, for the `predicateOrder`, you can -use the abbreviated form if it is a known `RdfPrefix` from Cool RDF's `Prefixes` enum -Error messages will give you hints. To configure the TTL formatting style, pass the configuration parameters under `` +The style options can be configured via spotless. Wherever the style wants a URI (for example, for the `predicateOrder`, you can +use the abbreviated form if it is a known `RdfPrefix` from Cool RDF's `Prefixes` enum. +Error messages will give you hints. To configure the TTL formatting style, pass the configuration parameters under ``. +For example, Cool RDF Formatter versions which support `preserveBlankNodeLabelsAndOrdering` (default true) can set it with +`false`.Set it to `false`, to use Cool RDF's default behavior (stable blank node ordering). ### Examples Minimal: @@ -1358,6 +1360,7 @@ Configuring some generic and TTL options: RIGHT true + false diff --git a/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java b/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java index 4a52313309..31a7dc914a 100644 --- a/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/rdf/RdfFormatterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 DiffPlug + * Copyright 2024-2026 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,9 @@ */ package com.diffplug.spotless.rdf; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -62,6 +65,39 @@ void testCoolRdfFormatter_2_0_0_style01() throws IOException, ClassNotFoundExcep testBeforeAfterFolders(inputDir, expectedOutputDir, StepHarness.forStep(forTurtleFormatterVersionAndStyle("2.0.0", style01()))); } + @Test + void blankNodeOrderingIsNotStableInCoolRdfFormatter_2_0_0() throws Exception { + FormatterStep step = forTurtleFormatterVersion("2.0.0"); + File file = new File("blank-node-order.ttl"); + + String alphaThenBeta = """ + @prefix ex: . + + ex:root + ex:child [ + ex:id "alpha" ; + ex:value "1" + ], [ + ex:id "beta" ; + ex:value "2" + ] . + """; + String betaThenAlpha = """ + @prefix ex: . + + ex:root + ex:child [ + ex:id "beta" ; + ex:value "2" + ], [ + ex:id "alpha" ; + ex:value "1" + ] . + """; + + assertThat(step.format(alphaThenBeta, file)).isNotEqualTo(step.format(betaThenAlpha, file)); + } + private static @NotNull Map defaultStyle() { return Map.of(); } From 4d53ab16015b1565fd98a5c0a5993519a7ccb56a Mon Sep 17 00:00:00 2001 From: Florian Kleedorfer Date: Thu, 9 Jul 2026 09:15:38 +0200 Subject: [PATCH 3/3] Format sources --- .../main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java | 2 +- .../main/java/com/diffplug/spotless/rdf/ReflectionHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java index b04d6140b4..b72d42d378 100644 --- a/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 DiffPlug + * Copyright 2024-2026 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java b/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java index 451e3a534b..f3459b7936 100644 --- a/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/rdf/ReflectionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 DiffPlug + * Copyright 2024-2026 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.