Skip to content

Commit a7ea252

Browse files
authored
feat: supply eclipse formatter settings as XML content (#2361)
2 parents af40183 + 0d0192d commit a7ea252

File tree

11 files changed

+185
-56
lines changed

11 files changed

+185
-56
lines changed

CHANGES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1212
## [Unreleased]
1313
### Added
1414
* Allow specifying path to Biome JSON config file directly in `biome` step. Requires biome 2.x. ([#2548](https://github.com/diffplug/spotless/pull/2548))
15-
- `GitPrePushHookInstaller`, a reusable library component for installing a Git `pre-push` hook that runs formatter checks.
16-
15+
* `GitPrePushHookInstaller`, a reusable library component for installing a Git `pre-push` hook that runs formatter checks. ([#2553](https://github.com/diffplug/spotless/pull/2553))
16+
* Allow setting Eclipse XML config from a string, not only from files ([#2361](https://github.com/diffplug/spotless/pull/2361))
1717
## Changed
1818
* Bump default `gson` version to latest `2.11.0` -> `2.13.1`. ([#2414](https://github.com/diffplug/spotless/pull/2414))
1919
* Bump default `jackson` version to latest `2.18.1` -> `2.19.2`. ([#2558](https://github.com/diffplug/spotless/pull/2558))

lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public abstract class EquoBasedStepBuilder {
5656
private String formatterVersion;
5757
private Iterable<File> settingsFiles = new ArrayList<>();
5858
private List<String> settingProperties = new ArrayList<>();
59+
private List<String> settingXml = new ArrayList<>();
5960
private Map<String, String> p2Mirrors = Map.of();
6061
private File cacheDirectory;
6162

@@ -86,6 +87,10 @@ public void setPropertyPreferences(List<String> propertyPreferences) {
8687
this.settingProperties = propertyPreferences;
8788
}
8889

90+
public void setXmlPreferences(List<String> settingXml) {
91+
this.settingXml = settingXml;
92+
}
93+
8994
public void setP2Mirrors(Map<String, String> p2Mirrors) {
9095
this.p2Mirrors = Map.copyOf(p2Mirrors);
9196
}
@@ -119,7 +124,7 @@ protected void addPlatformRepo(P2Model model, String version) {
119124

120125
/** Returns the FormatterStep (whose state will be calculated lazily). */
121126
public FormatterStep build() {
122-
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, FileSignature.promise(settingsFiles), JarState.promise(() -> {
127+
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, settingXml, FileSignature.promise(settingsFiles), JarState.promise(() -> {
123128
P2QueryResult query;
124129
try {
125130
if (null != cacheDirectory) {
@@ -174,23 +179,26 @@ static class EquoStep implements Serializable {
174179
private final JarState.Promised jarPromise;
175180
private final ImmutableMap<String, String> stepProperties;
176181
private List<String> settingProperties;
182+
private List<String> settingXml;
177183

178184
EquoStep(
179185
String semanticVersion,
180186
List<String> settingProperties,
187+
List<String> settingXml,
181188
FileSignature.Promised settingsPromise,
182189
JarState.Promised jarPromise,
183190
ImmutableMap<String, String> stepProperties) {
184191

185192
this.semanticVersion = semanticVersion;
186193
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
194+
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
187195
this.settingsPromise = settingsPromise;
188196
this.jarPromise = jarPromise;
189197
this.stepProperties = stepProperties;
190198
}
191199

192200
private State state() {
193-
return new State(semanticVersion, jarPromise.get(), settingProperties, settingsPromise.get(), stepProperties);
201+
return new State(semanticVersion, jarPromise.get(), settingProperties, settingXml, settingsPromise.get(), stepProperties);
194202
}
195203
}
196204

@@ -205,11 +213,13 @@ public static class State implements Serializable {
205213
final FileSignature settingsFiles;
206214
final ImmutableMap<String, String> stepProperties;
207215
private List<String> settingProperties;
216+
private List<String> settingXml;
208217

209-
public State(String semanticVersion, JarState jarState, List<String> settingProperties, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
218+
public State(String semanticVersion, JarState jarState, List<String> settingProperties, List<String> settingXml, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
210219
this.semanticVersion = semanticVersion;
211220
this.jarState = jarState;
212221
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
222+
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
213223
this.settingsFiles = settingsFiles;
214224
this.stepProperties = stepProperties;
215225
}
@@ -225,7 +235,8 @@ public String getSemanticVersion() {
225235
public Properties getPreferences() {
226236
FormatterProperties fromFiles = FormatterProperties.from(settingsFiles.files());
227237
FormatterProperties fromPropertiesContent = FormatterProperties.fromPropertiesContent(settingProperties);
228-
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties()).getProperties();
238+
FormatterProperties fromXmlContent = FormatterProperties.fromXmlContent(settingXml);
239+
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties(), fromXmlContent.getProperties()).getProperties();
229240
}
230241

231242
public ImmutableMap<String, String> getStepProperties() {

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

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2024 DiffPlug
2+
* Copyright 2016-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.io.ByteArrayInputStream;
2121
import java.io.File;
2222
import java.io.FileInputStream;
23+
import java.io.FileNotFoundException;
2324
import java.io.IOException;
2425
import java.io.InputStream;
2526
import java.nio.charset.StandardCharsets;
@@ -28,6 +29,7 @@
2829
import java.util.List;
2930
import java.util.Objects;
3031
import java.util.Properties;
32+
import java.util.function.Supplier;
3133
import java.util.stream.Collectors;
3234
import java.util.stream.IntStream;
3335

@@ -90,6 +92,25 @@ public static FormatterProperties fromPropertiesContent(Iterable<String> content
9092
return properties;
9193
}
9294

95+
public static FormatterProperties fromXmlContent(final Iterable<String> content) throws IllegalArgumentException {
96+
final List<String> nonNullElements = toNullHostileList(content);
97+
final FormatterProperties properties = new FormatterProperties();
98+
nonNullElements.forEach(contentElement -> {
99+
try {
100+
final Properties newSettings = FileParser.XML.executeXmlContent(contentElement);
101+
properties.properties.putAll(newSettings);
102+
} catch (IOException | IllegalArgumentException exception) {
103+
String message = String.format("Failed to add preferences from XML:%n%s%n", contentElement);
104+
final String detailedMessage = exception.getMessage();
105+
if (null != detailedMessage) {
106+
message += String.format(" %s", detailedMessage);
107+
}
108+
throw new IllegalArgumentException(message, exception);
109+
}
110+
});
111+
return properties;
112+
}
113+
93114
public static FormatterProperties merge(Properties... properties) {
94115
FormatterProperties merged = new FormatterProperties();
95116
List.of(properties).stream().forEach((source) -> merged.properties.putAll(source));
@@ -139,20 +160,45 @@ protected Properties execute(final File file) throws IOException, IllegalArgumen
139160
}
140161
return properties;
141162
}
163+
164+
@Override
165+
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
166+
throw new RuntimeException("Not implemented");
167+
}
142168
},
143169

144170
XML("xml") {
145171
@Override
146172
protected Properties execute(final File file) throws IOException, IllegalArgumentException {
147-
Node rootNode = getRootNode(file);
148-
String nodeName = rootNode.getNodeName();
149-
if (null == nodeName) {
150-
throw new IllegalArgumentException("XML document does not contain a root node.");
173+
return executeWithSupplier(() -> {
174+
try {
175+
return new FileInputStream(file);
176+
} catch (FileNotFoundException e) {
177+
throw new RuntimeException("File not found: " + file, e);
178+
}
179+
});
180+
}
181+
182+
@Override
183+
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
184+
return executeWithSupplier(() -> new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
185+
}
186+
187+
private Properties executeWithSupplier(Supplier<InputStream> isSupplier) throws IOException, IllegalArgumentException {
188+
Node rootNode;
189+
try (InputStream input = isSupplier.get()) {
190+
rootNode = getRootNode(input);
191+
String nodeName = rootNode.getNodeName();
192+
if (null == nodeName) {
193+
throw new IllegalArgumentException("XML document does not contain a root node.");
194+
}
195+
}
196+
try (InputStream input = isSupplier.get()) {
197+
return XmlParser.parse(input, rootNode);
151198
}
152-
return XmlParser.parse(file, rootNode);
153199
}
154200

155-
private Node getRootNode(final File file) throws IOException, IllegalArgumentException {
201+
private Node getRootNode(final InputStream is) throws IOException, IllegalArgumentException {
156202
try {
157203
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
158204
/*
@@ -166,7 +212,7 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
166212
*/
167213
dbf.setFeature(LOAD_EXTERNAL_DTD_PROP, false);
168214
DocumentBuilder db = dbf.newDocumentBuilder();
169-
return db.parse(file).getDocumentElement();
215+
return db.parse(is).getDocumentElement();
170216
} catch (SAXException | ParserConfigurationException e) {
171217
throw new IllegalArgumentException("File has no valid XML syntax.", e);
172218
}
@@ -186,6 +232,8 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
186232

187233
protected abstract Properties execute(File file) throws IOException, IllegalArgumentException;
188234

235+
protected abstract Properties executeXmlContent(String content) throws IOException, IllegalArgumentException;
236+
189237
public static Properties parse(final File file) throws IOException, IllegalArgumentException {
190238
String fileNameExtension = getFileNameExtension(file);
191239
for (FileParser parser : FileParser.values()) {
@@ -211,19 +259,17 @@ private static String getFileNameExtension(File file) {
211259
private enum XmlParser {
212260
PROPERTIES("properties") {
213261
@Override
214-
protected Properties execute(final File xmlFile, final Node rootNode)
262+
protected Properties execute(final InputStream xmlFile, final Node rootNode)
215263
throws IOException, IllegalArgumentException {
216264
final Properties properties = new Properties();
217-
try (InputStream xmlInput = new FileInputStream(xmlFile)) {
218-
properties.loadFromXML(xmlInput);
219-
}
265+
properties.loadFromXML(xmlFile);
220266
return properties;
221267
}
222268
},
223269

224270
PROFILES("profiles") {
225271
@Override
226-
protected Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException {
272+
protected Properties execute(InputStream file, Node rootNode) throws IOException, IllegalArgumentException {
227273
final Properties properties = new Properties();
228274
Node firstProfile = getSingleProfile(rootNode);
229275
for (Object settingObj : getChildren(firstProfile, "setting")) {
@@ -285,14 +331,14 @@ public String toString() {
285331
return this.rootNodeName;
286332
}
287333

288-
protected abstract Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException;
334+
protected abstract Properties execute(InputStream is, Node rootNode) throws IOException, IllegalArgumentException;
289335

290-
public static Properties parse(final File file, final Node rootNode)
336+
public static Properties parse(final InputStream is, final Node rootNode)
291337
throws IOException, IllegalArgumentException {
292338
String rootNodeName = rootNode.getNodeName();
293339
for (XmlParser parser : XmlParser.values()) {
294340
if (parser.rootNodeName.equals(rootNodeName)) {
295-
return parser.execute(file, rootNode);
341+
return parser.execute(is, rootNode);
296342
}
297343
}
298344
String msg = String.format("The XML root node '%1$s' is not part of the supported root nodes [%2$s].",

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
55
## [Unreleased]
66
### Added
77
* Allow specifying path to Biome JSON config file directly in `biome` step. Requires biome 2.x. ([#2548](https://github.com/diffplug/spotless/pull/2548))
8-
- `spotlessInstallGitPrePushHook` task, which installs a Git `pre-push` hook to run `spotlessCheck` and `spotlessApply`.
9-
Uses shared implementation from `GitPrePushHookInstaller`.
10-
[#2553](https://github.com/diffplug/spotless/pull/2553)
11-
8+
* `spotlessInstallGitPrePushHook` task, which installs a Git `pre-push` hook to run `spotlessCheck` and `spotlessApply`. ([#2553](https://github.com/diffplug/spotless/pull/2553))
9+
* Allow setting Eclipse XML config from a string, not only from files ([#2361](https://github.com/diffplug/spotless/pull/2361))
1210
## Changed
1311
* Bump default `gson` version to latest `2.11.0` -> `2.13.1`. ([#2414](https://github.com/diffplug/spotless/pull/2414))
1412
* Bump default `jackson` version to latest `2.18.1` -> `2.19.2`. ([#2558](https://github.com/diffplug/spotless/pull/2558))

plugin-gradle/README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,14 @@ spotless {
291291
eclipse()
292292
// optional: you can specify a specific version and/or config file
293293
eclipse('4.26').configFile('eclipse-prefs.xml')
294-
// Or supply the configuration as a string
294+
// Or supply the configuration properties as a string
295295
eclipse('4.26').configProperties("""
296296
...
297297
""")
298+
// Or supply the configuration XML as a string
299+
eclipse('4.26').configXml("""
300+
...
301+
""")
298302
// if the access to the p2 repositories is restricted, mirrors can be
299303
// specified using a URI prefix map as follows:
300304
eclipse().withP2Mirrors(['https://download.eclipse.org/eclipse/updates/4.29/':'https://some.internal.mirror/4-29-updates-p2/'])
@@ -451,10 +455,14 @@ spotless {
451455
greclipse()
452456
// optional: you can specify a specific version or config file(s), version matches the Eclipse Platform
453457
greclipse('4.26').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs')
454-
// Or supply the configuration as a string
458+
// Or supply the configuration properties as a string
455459
greclipse('4.26').configProperties("""
456460
...
457461
""")
462+
// Or supply the configuration XML as a string
463+
greclipse('4.26').configXml("""
464+
...
465+
""")
458466
```
459467

460468
Groovy-Eclipse formatting errors/warnings lead per default to a build failure. This behavior can be changed by adding the property/key value `ignoreFormatterProblems=true` to a configuration file. In this scenario, files causing problems, will not be modified by this formatter step.
@@ -619,10 +627,14 @@ spotles {
619627
cpp {
620628
// version and configFile are both optional
621629
eclipseCdt('4.13.0').configFile('eclipse-cdt.xml')
622-
// Or supply the configuration as a string
630+
// Or supply the configuration properties as a string
623631
eclipseCdt('4.13.0').configProperties("""
624632
...
625633
""")
634+
// Or supply the configuration XML as a string
635+
eclipseCdt('4.13.0').configXml("""
636+
...
637+
""")
626638
}
627639
}
628640
```

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseGroovyExtension.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 DiffPlug
2+
* Copyright 2023-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,6 +80,13 @@ public GrEclipseConfig configProperties(String... configs) {
8080
return this;
8181
}
8282

83+
public GrEclipseConfig configXml(String... configs) {
84+
requireElementsNonNull(configs);
85+
builder.setXmlPreferences(List.of(configs));
86+
extension.replaceStep(builder.build());
87+
return this;
88+
}
89+
8390
public GrEclipseConfig withP2Mirrors(Map<String, String> mirrors) {
8491
builder.setP2Mirrors(mirrors);
8592
extension.replaceStep(builder.build());

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2024 DiffPlug
2+
* Copyright 2016-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -69,6 +69,13 @@ public EclipseConfig configProperties(String... configs) {
6969
return this;
7070
}
7171

72+
public EclipseConfig configXml(String... configs) {
73+
requireElementsNonNull(configs);
74+
builder.setXmlPreferences(List.of(configs));
75+
replaceStep(builder.build());
76+
return this;
77+
}
78+
7279
public EclipseConfig withP2Mirrors(Map<String, String> mirrors) {
7380
builder.setP2Mirrors(mirrors);
7481
replaceStep(builder.build());

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,13 @@ public EclipseConfig configProperties(String... configs) {
317317
return this;
318318
}
319319

320+
public EclipseConfig configXml(String... configs) {
321+
requireElementsNonNull(configs);
322+
builder.setXmlPreferences(List.of(configs));
323+
replaceStep(builder.build());
324+
return this;
325+
}
326+
320327
public EclipseConfig sortMembersDoNotSortFields(boolean doNotSortFields) {
321328
builder.sortMembersDoNotSortFields(doNotSortFields);
322329
replaceStep(builder.build());

0 commit comments

Comments
 (0)