Skip to content

Commit dbe72d8

Browse files
authored
RDF Formatter Contribution (#2261)
2 parents 9b8d995 + 9d85ff8 commit dbe72d8

File tree

21 files changed

+6903
-8
lines changed

21 files changed

+6903
-8
lines changed

CHANGES.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ This document is intended for Spotless developers.
1010
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
1111

1212
## [Unreleased]
13+
### Added
14+
* Support for `rdf` ([#2261](https://github.com/diffplug/spotless/pull/2261))
1315
### Changed
1416
* Support configuring the Equo P2 cache. ([#2238](https://github.com/diffplug/spotless/pull/2238))
15-
1617
* Add explicit support for JSONC / CSS via biome, via the file extensions `.css` and `.jsonc`.
1718
([#2259](https://github.com/diffplug/spotless/pull/2259))
1819

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}}
103103
lib('pom.SortPomStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
104104
lib('protobuf.BufStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
105105
lib('python.BlackStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
106+
lib('rdf.RdfFormatterStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
106107
lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
107108
lib('shell.ShfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
108109
lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
@@ -157,6 +158,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}}
157158
| [`pom.SortPomStep`](lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
158159
| [`protobuf.BufStep`](lib/src/main/java/com/diffplug/spotless/protobuf/BufStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
159160
| [`python.BlackStep`](lib/src/main/java/com/diffplug/spotless/python/BlackStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
161+
| [`rdf.RdfFormatterStep`](lib/src/main/java/com/diffplug/spotless/rdf/RdfFormatterStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
160162
| [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
161163
| [`shell.ShfmtStep`](lib/src/main/java/com/diffplug/spotless/shell/ShfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
162164
| [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |

lib/src/main/java/com/diffplug/spotless/Formatter.java

-2
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ public boolean isClean(File file) throws IOException {
165165

166166
String raw = new String(Files.readAllBytes(file.toPath()), encoding);
167167
String unix = LineEnding.toUnix(raw);
168-
169168
// check the newlines (we can find these problems without even running the steps)
170169
int totalNewLines = (int) unix.codePoints().filter(val -> val == '\n').count();
171170
int windowsNewLines = raw.length() - unix.length();
@@ -181,7 +180,6 @@ public boolean isClean(File file) throws IOException {
181180

182181
// check the other formats
183182
String formatted = compute(unix, file);
184-
185183
// return true iff the formatted string equals the unix one
186184
return formatted.equals(unix);
187185
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.rdf;
17+
18+
import java.io.Serializable;
19+
import java.util.Objects;
20+
21+
public class RdfFormatterConfig implements Serializable {
22+
private static final long serialVersionUID = 1L;
23+
private boolean failOnWarning = true;
24+
private String turtleFormatterVersion = RdfFormatterStep.LATEST_TURTLE_FORMATTER_VERSION;
25+
private boolean verify = true;
26+
27+
public RdfFormatterConfig() {}
28+
29+
public void setFailOnWarning(boolean failOnWarning) {
30+
this.failOnWarning = failOnWarning;
31+
}
32+
33+
public boolean isFailOnWarning() {
34+
return failOnWarning;
35+
}
36+
37+
public boolean isVerify() {
38+
return verify;
39+
}
40+
41+
public void setVerify(boolean verify) {
42+
this.verify = verify;
43+
}
44+
45+
public static Builder builder() {
46+
return new Builder();
47+
}
48+
49+
public String getTurtleFormatterVersion() {
50+
return turtleFormatterVersion;
51+
}
52+
53+
public void setTurtleFormatterVersion(String turtleFormatterVersion) {
54+
this.turtleFormatterVersion = turtleFormatterVersion;
55+
}
56+
57+
public static class Builder {
58+
RdfFormatterConfig config = new RdfFormatterConfig();
59+
60+
public Builder() {}
61+
62+
public Builder failOnWarning() {
63+
return this.failOnWarning(true);
64+
}
65+
66+
public Builder failOnWarning(boolean fail) {
67+
this.config.setFailOnWarning(fail);
68+
return this;
69+
}
70+
71+
public Builder turtleFormatterVersion(String version) {
72+
this.config.turtleFormatterVersion = version;
73+
return this;
74+
}
75+
76+
public Builder verify(boolean verify) {
77+
this.config.verify = verify;
78+
return this;
79+
}
80+
81+
public Builder verify() {
82+
this.config.verify = true;
83+
return this;
84+
}
85+
86+
public RdfFormatterConfig build() {
87+
return config;
88+
}
89+
}
90+
91+
@Override
92+
public boolean equals(Object o) {
93+
if (this == o)
94+
return true;
95+
if (!(o instanceof RdfFormatterConfig))
96+
return false;
97+
RdfFormatterConfig that = (RdfFormatterConfig) o;
98+
return isFailOnWarning() == that.isFailOnWarning()
99+
&& Objects.equals(turtleFormatterVersion, that.turtleFormatterVersion);
100+
}
101+
102+
@Override
103+
public int hashCode() {
104+
return Objects.hash(isFailOnWarning(), turtleFormatterVersion);
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.rdf;
17+
18+
import java.io.File;
19+
import java.lang.reflect.InvocationTargetException;
20+
import java.util.List;
21+
import java.util.Locale;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
25+
import com.diffplug.spotless.FormatterFunc;
26+
import com.diffplug.spotless.LineEnding;
27+
28+
public class RdfFormatterFunc implements FormatterFunc {
29+
private static final Set<String> TURTLE_EXTENSIONS = Set.of("ttl", "turtle");
30+
private static final Set<String> TRIG_EXTENSIONS = Set.of("trig");
31+
private static final Set<String> NTRIPLES_EXTENSIONS = Set.of("n-triples", "ntriples", "nt");
32+
private static final Set<String> NQUADS_EXTENSIONS = Set.of("n-quads", "nquads", "nq");
33+
34+
private final RdfFormatterStep.State state;
35+
private final ReflectionHelper reflectionHelper;
36+
37+
public RdfFormatterFunc(RdfFormatterStep.State state)
38+
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
39+
this.state = state;
40+
this.reflectionHelper = new ReflectionHelper(state);
41+
}
42+
43+
@Override
44+
public String apply(String input) throws Exception {
45+
throw new UnsupportedOperationException("We need to know the filename so we can guess the RDF format. Use apply(String, File) instead!");
46+
}
47+
48+
@Override
49+
public String apply(String rawUnix, File file) throws Exception {
50+
String filename = file.getName().toLowerCase(Locale.US);
51+
int lastDot = filename.lastIndexOf('.');
52+
if (lastDot < 0) {
53+
throw new IllegalArgumentException(
54+
String.format("File %s has no file extension, cannot determine RDF format", file.getAbsolutePath()));
55+
}
56+
if (lastDot + 1 >= filename.length()) {
57+
throw new IllegalArgumentException(
58+
String.format("File %s has no file extension, cannot determine RDF format", file.getAbsolutePath()));
59+
}
60+
String extension = filename.substring(lastDot + 1);
61+
62+
try {
63+
if (TURTLE_EXTENSIONS.contains(extension)) {
64+
return formatTurtle(rawUnix, file, reflectionHelper);
65+
}
66+
if (TRIG_EXTENSIONS.contains(extension)) {
67+
return formatTrig(rawUnix, file);
68+
}
69+
if (NTRIPLES_EXTENSIONS.contains(extension)) {
70+
return formatNTriples(rawUnix, file);
71+
}
72+
if (NQUADS_EXTENSIONS.contains(extension)) {
73+
return formatNQuads(rawUnix, file);
74+
}
75+
throw new IllegalArgumentException(String.format("Cannot handle file with extension %s", extension));
76+
} catch (InvocationTargetException e) {
77+
throw new RuntimeException("Error formatting file " + file.getPath(), e.getCause());
78+
} catch (Exception e) {
79+
throw new RuntimeException("Error formatting file " + file.getPath(), e);
80+
}
81+
}
82+
83+
private String formatNQuads(String rawUnix, File file) {
84+
throw new UnsupportedOperationException("NQUADS formatting not supported yet");
85+
}
86+
87+
private String formatNTriples(String rawUnix, File file) {
88+
throw new UnsupportedOperationException("NTRIPLES formatting not supported yet");
89+
}
90+
91+
private String formatTrig(String rawUnix, File file) {
92+
throw new UnsupportedOperationException("TRIG formatting not supported yet");
93+
}
94+
95+
private String formatTurtle(String rawUnix, File file, ReflectionHelper reflectionHelper)
96+
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException,
97+
NoSuchFieldException, InstantiationException {
98+
String formatted;
99+
Object lang = reflectionHelper.getLang("TTL");
100+
formatted = reflectionHelper.formatWithTurtleFormatter(rawUnix);
101+
if (state.getConfig().isVerify()) {
102+
veryfyResult(rawUnix, file, reflectionHelper, lang, formatted);
103+
}
104+
return LineEnding.toUnix(formatted);
105+
}
106+
107+
private static void veryfyResult(String rawUnix, File file, ReflectionHelper reflectionHelper, Object lang,
108+
String formatted) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
109+
Object modelBefore = reflectionHelper.parseToModel(rawUnix, file, lang);
110+
Object modelAfter = reflectionHelper.parseToModel(formatted, file, lang);
111+
if (!reflectionHelper.areModelsIsomorphic(modelBefore, modelAfter)) {
112+
long beforeSize = reflectionHelper.modelSize(modelBefore);
113+
long afterSize = reflectionHelper.modelSize(modelAfter);
114+
String diffResult;
115+
if (beforeSize != afterSize) {
116+
diffResult = String.format("< %,d triples", beforeSize);
117+
diffResult += String.format("> %,d triples", afterSize);
118+
} else {
119+
diffResult = calculateDiff(reflectionHelper, modelBefore, modelAfter);
120+
}
121+
throw new IllegalStateException(
122+
"Formatted RDF is not isomorphic with original, which means that formatting changed the data.\n"
123+
+ "This could be a bug in the formatting system leading to data corruption and should be reported. \n"
124+
+ "If you are not scared to lose data, you can disable this check by setting the config option 'verify' to 'false'"
125+
+ "\n\nDiff:\n"
126+
+ diffResult);
127+
}
128+
}
129+
130+
private static String calculateDiff(ReflectionHelper reflectionHelper, Object modelBefore, Object modelAfter)
131+
throws InvocationTargetException, IllegalAccessException {
132+
String diffResult;
133+
Object graphBefore = reflectionHelper.getGraph(modelBefore);
134+
Object graphAfter = reflectionHelper.getGraph(modelAfter);
135+
136+
List<Object> onlyInBeforeContent = reflectionHelper.streamGraph(graphBefore)
137+
.filter(triple -> {
138+
try {
139+
return !reflectionHelper.graphContainsSameTerm(graphAfter, triple);
140+
} catch (InvocationTargetException | IllegalAccessException e) {
141+
throw new RuntimeException(e);
142+
}
143+
})
144+
.collect(Collectors.toList());
145+
146+
List<Object> onlyInAfterContent = reflectionHelper.streamGraph(graphAfter)
147+
.filter(triple -> {
148+
try {
149+
return !reflectionHelper.graphContainsSameTerm(graphBefore, triple);
150+
} catch (InvocationTargetException | IllegalAccessException e) {
151+
throw new RuntimeException(e);
152+
}
153+
})
154+
.collect(Collectors.toList());
155+
if (!(onlyInBeforeContent.isEmpty() && onlyInAfterContent.isEmpty())) {
156+
diffResult = onlyInBeforeContent.stream().map(s -> String.format("< %s", s))
157+
.collect(Collectors.joining("\n"));
158+
diffResult += "\n" + onlyInAfterContent.stream().map(s -> String.format("> %s", s)).collect(Collectors.joining("\n"));
159+
} else {
160+
diffResult = "'before' and 'after' content differs, but we don't know why. This is probably a bug.";
161+
}
162+
return diffResult;
163+
}
164+
165+
}

0 commit comments

Comments
 (0)