Skip to content
4 changes: 2 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
* Allow specifying path to Biome JSON config file directly in `biome` step. Requires biome 2.x. ([#2548](https://github.com/diffplug/spotless/pull/2548))
- `GitPrePushHookInstaller`, a reusable library component for installing a Git `pre-push` hook that runs formatter checks.

* `GitPrePushHookInstaller`, a reusable library component for installing a Git `pre-push` hook that runs formatter checks. ([#2553](https://github.com/diffplug/spotless/pull/2553))
* Allow setting Eclipse XML config from a string, not only from files ([#2361](https://github.com/diffplug/spotless/pull/2361))
## Changed
* Bump default `gson` version to latest `2.11.0` -> `2.13.1`. ([#2414](https://github.com/diffplug/spotless/pull/2414))
* Bump default `jackson` version to latest `2.18.1` -> `2.19.2`. ([#2558](https://github.com/diffplug/spotless/pull/2558))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public abstract class EquoBasedStepBuilder {
private String formatterVersion;
private Iterable<File> settingsFiles = new ArrayList<>();
private List<String> settingProperties = new ArrayList<>();
private List<String> settingXml = new ArrayList<>();
private Map<String, String> p2Mirrors = Map.of();
private File cacheDirectory;

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

public void setXmlPreferences(List<String> settingXml) {
this.settingXml = settingXml;
}

public void setP2Mirrors(Map<String, String> p2Mirrors) {
this.p2Mirrors = Map.copyOf(p2Mirrors);
}
Expand Down Expand Up @@ -119,7 +124,7 @@ protected void addPlatformRepo(P2Model model, String version) {

/** Returns the FormatterStep (whose state will be calculated lazily). */
public FormatterStep build() {
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, FileSignature.promise(settingsFiles), JarState.promise(() -> {
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, settingXml, FileSignature.promise(settingsFiles), JarState.promise(() -> {
P2QueryResult query;
try {
if (null != cacheDirectory) {
Expand Down Expand Up @@ -174,23 +179,26 @@ static class EquoStep implements Serializable {
private final JarState.Promised jarPromise;
private final ImmutableMap<String, String> stepProperties;
private List<String> settingProperties;
private List<String> settingXml;

EquoStep(
String semanticVersion,
List<String> settingProperties,
List<String> settingXml,
FileSignature.Promised settingsPromise,
JarState.Promised jarPromise,
ImmutableMap<String, String> stepProperties) {

this.semanticVersion = semanticVersion;
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
this.settingsPromise = settingsPromise;
this.jarPromise = jarPromise;
this.stepProperties = stepProperties;
}

private State state() {
return new State(semanticVersion, jarPromise.get(), settingProperties, settingsPromise.get(), stepProperties);
return new State(semanticVersion, jarPromise.get(), settingProperties, settingXml, settingsPromise.get(), stepProperties);
}
}

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

public State(String semanticVersion, JarState jarState, List<String> settingProperties, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
public State(String semanticVersion, JarState jarState, List<String> settingProperties, List<String> settingXml, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
this.semanticVersion = semanticVersion;
this.jarState = jarState;
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
this.settingsFiles = settingsFiles;
this.stepProperties = stepProperties;
}
Expand All @@ -225,7 +235,8 @@ public String getSemanticVersion() {
public Properties getPreferences() {
FormatterProperties fromFiles = FormatterProperties.from(settingsFiles.files());
FormatterProperties fromPropertiesContent = FormatterProperties.fromPropertiesContent(settingProperties);
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties()).getProperties();
FormatterProperties fromXmlContent = FormatterProperties.fromXmlContent(settingXml);
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties(), fromXmlContent.getProperties()).getProperties();
}

public ImmutableMap<String, String> getStepProperties() {
Expand Down
78 changes: 62 additions & 16 deletions lib/src/main/java/com/diffplug/spotless/FormatterProperties.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 DiffPlug
* Copyright 2016-2025 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -28,6 +29,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand Down Expand Up @@ -90,6 +92,25 @@ public static FormatterProperties fromPropertiesContent(Iterable<String> content
return properties;
}

public static FormatterProperties fromXmlContent(final Iterable<String> content) throws IllegalArgumentException {
final List<String> nonNullElements = toNullHostileList(content);
final FormatterProperties properties = new FormatterProperties();
nonNullElements.forEach(contentElement -> {
try {
final Properties newSettings = FileParser.XML.executeXmlContent(contentElement);
properties.properties.putAll(newSettings);
} catch (IOException | IllegalArgumentException exception) {
String message = String.format("Failed to add preferences from XML:%n%s%n", contentElement);
final String detailedMessage = exception.getMessage();
if (null != detailedMessage) {
message += String.format(" %s", detailedMessage);
}
throw new IllegalArgumentException(message, exception);
}
});
return properties;
}

public static FormatterProperties merge(Properties... properties) {
FormatterProperties merged = new FormatterProperties();
List.of(properties).stream().forEach((source) -> merged.properties.putAll(source));
Expand Down Expand Up @@ -139,20 +160,45 @@ protected Properties execute(final File file) throws IOException, IllegalArgumen
}
return properties;
}

@Override
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
throw new RuntimeException("Not implemented");
}
},

XML("xml") {
@Override
protected Properties execute(final File file) throws IOException, IllegalArgumentException {
Node rootNode = getRootNode(file);
String nodeName = rootNode.getNodeName();
if (null == nodeName) {
throw new IllegalArgumentException("XML document does not contain a root node.");
return executeWithSupplier(() -> {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException("File not found: " + file, e);
}
});
}

@Override
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
return executeWithSupplier(() -> new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}

private Properties executeWithSupplier(Supplier<InputStream> isSupplier) throws IOException, IllegalArgumentException {
Node rootNode;
try (InputStream input = isSupplier.get()) {
rootNode = getRootNode(input);
String nodeName = rootNode.getNodeName();
if (null == nodeName) {
throw new IllegalArgumentException("XML document does not contain a root node.");
}
}
try (InputStream input = isSupplier.get()) {
return XmlParser.parse(input, rootNode);
}
return XmlParser.parse(file, rootNode);
}

private Node getRootNode(final File file) throws IOException, IllegalArgumentException {
private Node getRootNode(final InputStream is) throws IOException, IllegalArgumentException {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
/*
Expand All @@ -166,7 +212,7 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
*/
dbf.setFeature(LOAD_EXTERNAL_DTD_PROP, false);
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(file).getDocumentElement();
return db.parse(is).getDocumentElement();
} catch (SAXException | ParserConfigurationException e) {
throw new IllegalArgumentException("File has no valid XML syntax.", e);
}
Expand All @@ -186,6 +232,8 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc

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

protected abstract Properties executeXmlContent(String content) throws IOException, IllegalArgumentException;

public static Properties parse(final File file) throws IOException, IllegalArgumentException {
String fileNameExtension = getFileNameExtension(file);
for (FileParser parser : FileParser.values()) {
Expand All @@ -211,19 +259,17 @@ private static String getFileNameExtension(File file) {
private enum XmlParser {
PROPERTIES("properties") {
@Override
protected Properties execute(final File xmlFile, final Node rootNode)
protected Properties execute(final InputStream xmlFile, final Node rootNode)
throws IOException, IllegalArgumentException {
final Properties properties = new Properties();
try (InputStream xmlInput = new FileInputStream(xmlFile)) {
properties.loadFromXML(xmlInput);
}
properties.loadFromXML(xmlFile);
return properties;
}
},

PROFILES("profiles") {
@Override
protected Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException {
protected Properties execute(InputStream file, Node rootNode) throws IOException, IllegalArgumentException {
final Properties properties = new Properties();
Node firstProfile = getSingleProfile(rootNode);
for (Object settingObj : getChildren(firstProfile, "setting")) {
Expand Down Expand Up @@ -285,14 +331,14 @@ public String toString() {
return this.rootNodeName;
}

protected abstract Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException;
protected abstract Properties execute(InputStream is, Node rootNode) throws IOException, IllegalArgumentException;

public static Properties parse(final File file, final Node rootNode)
public static Properties parse(final InputStream is, final Node rootNode)
throws IOException, IllegalArgumentException {
String rootNodeName = rootNode.getNodeName();
for (XmlParser parser : XmlParser.values()) {
if (parser.rootNodeName.equals(rootNodeName)) {
return parser.execute(file, rootNode);
return parser.execute(is, rootNode);
}
}
String msg = String.format("The XML root node '%1$s' is not part of the supported root nodes [%2$s].",
Expand Down
6 changes: 2 additions & 4 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [Unreleased]
### Added
* Allow specifying path to Biome JSON config file directly in `biome` step. Requires biome 2.x. ([#2548](https://github.com/diffplug/spotless/pull/2548))
- `spotlessInstallGitPrePushHook` task, which installs a Git `pre-push` hook to run `spotlessCheck` and `spotlessApply`.
Uses shared implementation from `GitPrePushHookInstaller`.
[#2553](https://github.com/diffplug/spotless/pull/2553)

* `spotlessInstallGitPrePushHook` task, which installs a Git `pre-push` hook to run `spotlessCheck` and `spotlessApply`. ([#2553](https://github.com/diffplug/spotless/pull/2553))
* Allow setting Eclipse XML config from a string, not only from files ([#2361](https://github.com/diffplug/spotless/pull/2361))
## Changed
* Bump default `gson` version to latest `2.11.0` -> `2.13.1`. ([#2414](https://github.com/diffplug/spotless/pull/2414))
* Bump default `jackson` version to latest `2.18.1` -> `2.19.2`. ([#2558](https://github.com/diffplug/spotless/pull/2558))
Expand Down
18 changes: 15 additions & 3 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,14 @@ spotless {
eclipse()
// optional: you can specify a specific version and/or config file
eclipse('4.26').configFile('eclipse-prefs.xml')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
eclipse('4.26').configProperties("""
...
""")
// Or supply the configuration XML as a string
eclipse('4.26').configXml("""
...
""")
// if the access to the p2 repositories is restricted, mirrors can be
// specified using a URI prefix map as follows:
eclipse().withP2Mirrors(['https://download.eclipse.org/eclipse/updates/4.29/':'https://some.internal.mirror/4-29-updates-p2/'])
Expand Down Expand Up @@ -451,10 +455,14 @@ spotless {
greclipse()
// optional: you can specify a specific version or config file(s), version matches the Eclipse Platform
greclipse('4.26').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
greclipse('4.26').configProperties("""
...
""")
// Or supply the configuration XML as a string
greclipse('4.26').configXml("""
...
""")
```

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.
Expand Down Expand Up @@ -619,10 +627,14 @@ spotles {
cpp {
// version and configFile are both optional
eclipseCdt('4.13.0').configFile('eclipse-cdt.xml')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
eclipseCdt('4.13.0').configProperties("""
...
""")
// Or supply the configuration XML as a string
eclipseCdt('4.13.0').configXml("""
...
""")
}
}
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 DiffPlug
* Copyright 2023-2025 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -80,6 +80,13 @@ public GrEclipseConfig configProperties(String... configs) {
return this;
}

public GrEclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
extension.replaceStep(builder.build());
return this;
}

public GrEclipseConfig withP2Mirrors(Map<String, String> mirrors) {
builder.setP2Mirrors(mirrors);
extension.replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2024 DiffPlug
* Copyright 2016-2025 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -69,6 +69,13 @@ public EclipseConfig configProperties(String... configs) {
return this;
}

public EclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
replaceStep(builder.build());
return this;
}

public EclipseConfig withP2Mirrors(Map<String, String> mirrors) {
builder.setP2Mirrors(mirrors);
replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,13 @@ public EclipseConfig configProperties(String... configs) {
return this;
}

public EclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
replaceStep(builder.build());
return this;
}

public EclipseConfig sortMembersDoNotSortFields(boolean doNotSortFields) {
builder.sortMembersDoNotSortFields(doNotSortFields);
replaceStep(builder.build());
Expand Down
Loading
Loading