Skip to content

Commit 6efa10a

Browse files
committed
feat: supply eclipse formatter settings as XML content
1 parent 671b6ee commit 6efa10a

File tree

9 files changed

+168
-38
lines changed

9 files changed

+168
-38
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1111

1212
## [Unreleased]
1313
### Changed
14+
* TODO ([#](https://github.com/diffplug/spotless/pull/X))
1415
* Allow setting Eclipse config from a string, not only from files ([#2337](https://github.com/diffplug/spotless/pull/2337))
1516
* Bump default `ktlint` version to latest `1.3.0` -> `1.4.0`. ([#2314](https://github.com/diffplug/spotless/pull/2314))
1617
* Add _Sort Members_ feature based on [Eclipse JDT](plugin-gradle/README.md#eclipse-jdt) implementation. ([#2312](https://github.com/diffplug/spotless/pull/2312))

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: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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,40 @@ 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);
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 = getRootNode(isSupplier.get());
148189
String nodeName = rootNode.getNodeName();
149190
if (null == nodeName) {
150191
throw new IllegalArgumentException("XML document does not contain a root node.");
151192
}
152-
return XmlParser.parse(file, rootNode);
193+
return XmlParser.parse(isSupplier.get(), rootNode);
153194
}
154195

155-
private Node getRootNode(final File file) throws IOException, IllegalArgumentException {
196+
private Node getRootNode(final InputStream is) throws IOException, IllegalArgumentException {
156197
try {
157198
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
158199
/*
@@ -166,7 +207,7 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
166207
*/
167208
dbf.setFeature(LOAD_EXTERNAL_DTD_PROP, false);
168209
DocumentBuilder db = dbf.newDocumentBuilder();
169-
return db.parse(file).getDocumentElement();
210+
return db.parse(is).getDocumentElement();
170211
} catch (SAXException | ParserConfigurationException e) {
171212
throw new IllegalArgumentException("File has no valid XML syntax.", e);
172213
}
@@ -186,6 +227,8 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
186227

187228
protected abstract Properties execute(File file) throws IOException, IllegalArgumentException;
188229

230+
protected abstract Properties executeXmlContent(String content) throws IOException, IllegalArgumentException;
231+
189232
public static Properties parse(final File file) throws IOException, IllegalArgumentException {
190233
String fileNameExtension = getFileNameExtension(file);
191234
for (FileParser parser : FileParser.values()) {
@@ -211,19 +254,17 @@ private static String getFileNameExtension(File file) {
211254
private enum XmlParser {
212255
PROPERTIES("properties") {
213256
@Override
214-
protected Properties execute(final File xmlFile, final Node rootNode)
257+
protected Properties execute(final InputStream xmlFile, final Node rootNode)
215258
throws IOException, IllegalArgumentException {
216259
final Properties properties = new Properties();
217-
try (InputStream xmlInput = new FileInputStream(xmlFile)) {
218-
properties.loadFromXML(xmlInput);
219-
}
260+
properties.loadFromXML(xmlFile);
220261
return properties;
221262
}
222263
},
223264

224265
PROFILES("profiles") {
225266
@Override
226-
protected Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException {
267+
protected Properties execute(InputStream file, Node rootNode) throws IOException, IllegalArgumentException {
227268
final Properties properties = new Properties();
228269
Node firstProfile = getSingleProfile(rootNode);
229270
for (Object settingObj : getChildren(firstProfile, "setting")) {
@@ -285,14 +326,14 @@ public String toString() {
285326
return this.rootNodeName;
286327
}
287328

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

290-
public static Properties parse(final File file, final Node rootNode)
331+
public static Properties parse(final InputStream is, final Node rootNode)
291332
throws IOException, IllegalArgumentException {
292333
String rootNodeName = rootNode.getNodeName();
293334
for (XmlParser parser : XmlParser.values()) {
294335
if (parser.rootNodeName.equals(rootNodeName)) {
295-
return parser.execute(file, rootNode);
336+
return parser.execute(is, rootNode);
296337
}
297338
}
298339
String msg = String.format("The XML root node '%1$s' is not part of the supported root nodes [%2$s].",

plugin-gradle/README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,14 @@ spotless {
262262
eclipse()
263263
// optional: you can specify a specific version and/or config file
264264
eclipse('4.26').configFile('eclipse-prefs.xml')
265-
// Or supply the configuration as a string
265+
// Or supply the configuration properties as a string
266266
eclipse('4.26').configProperties("""
267267
...
268268
""")
269+
// Or supply the configuration XML as a string
270+
eclipse('4.26').configXml("""
271+
...
272+
""")
269273
// if the access to the p2 repositories is restricted, mirrors can be
270274
// specified using a URI prefix map as follows:
271275
eclipse().withP2Mirrors(['https://download.eclipse.org/eclipse/updates/4.29/':'https://some.internal.mirror/4-29-updates-p2/'])
@@ -422,10 +426,14 @@ spotless {
422426
greclipse()
423427
// optional: you can specify a specific version or config file(s), version matches the Eclipse Platform
424428
greclipse('4.26').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs')
425-
// Or supply the configuration as a string
429+
// Or supply the configuration properties as a string
426430
greclipse('4.26').configProperties("""
427431
...
428432
""")
433+
// Or supply the configuration XML as a string
434+
greclipse('4.26').configXml("""
435+
...
436+
""")
429437
```
430438

431439
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.
@@ -580,10 +588,14 @@ spotles {
580588
cpp {
581589
// version and configFile are both optional
582590
eclipseCdt('4.13.0').configFile('eclipse-cdt.xml')
583-
// Or supply the configuration as a string
591+
// Or supply the configuration properties as a string
584592
eclipseCdt('4.13.0').configProperties("""
585593
...
586594
""")
595+
// Or supply the configuration XML as a string
596+
eclipseCdt('4.13.0').configXml("""
597+
...
598+
""")
587599
}
588600
}
589601
```

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -312,6 +312,13 @@ public EclipseConfig configProperties(String... configs) {
312312
return this;
313313
}
314314

315+
public EclipseConfig configXml(String... configs) {
316+
requireElementsNonNull(configs);
317+
builder.setXmlPreferences(List.of(configs));
318+
replaceStep(builder.build());
319+
return this;
320+
}
321+
315322
public EclipseConfig sortMembersDoNotSortFields(boolean doNotSortFields) {
316323
builder.sortMembersDoNotSortFields(doNotSortFields);
317324
replaceStep(builder.build());

plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaEclipseTest.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
class JavaEclipseTest extends GradleIntegrationHarness {
2323
@Test
24-
void settingsWithContentWithoutFile() throws IOException {
24+
void settingsWithProprtiesContent() throws IOException {
2525
setFile("build.gradle").toLines(
2626
"plugins {",
2727
" id 'com.diffplug.spotless'",
@@ -37,4 +37,27 @@ void settingsWithContentWithoutFile() throws IOException {
3737

3838
gradleRunner().withArguments("spotlessApply").build();
3939
}
40+
41+
@Test
42+
void settingsWithXmlContent() throws IOException {
43+
setFile("build.gradle").toLines(
44+
"plugins {",
45+
" id 'com.diffplug.spotless'",
46+
" id 'java'",
47+
"}",
48+
"repositories { mavenCentral() }",
49+
"",
50+
"spotless {",
51+
" java { eclipse().configProperties(\"\"\"",
52+
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>",
53+
"<profiles version=\"12\">",
54+
" <profile kind=\"CodeFormatterProfile\" name=\"Spotless\" version=\"12\">",
55+
" <setting id=\"valid_line_oriented.prefs.string\" value=\"string\" />",
56+
" </profile>",
57+
"</profiles>",
58+
"\"\"\") }",
59+
"}");
60+
61+
gradleRunner().withArguments("spotlessApply").build();
62+
}
4063
}

0 commit comments

Comments
 (0)