Skip to content

Commit 5951f3d

Browse files
authored
Added support for Maven POM sorting/formatting (#946)
2 parents a9e8357 + 617abf5 commit 5951f3d

File tree

18 files changed

+631
-26
lines changed

18 files changed

+631
-26
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1212
## [Unreleased]
1313
### Added
1414
* Added support for custom JSR223 formatters ([#945](https://github.com/diffplug/spotless/pull/945))
15+
* Added support for formating and sorting Maven POMs ([#946](https://github.com/diffplug/spotless/pull/946))
1516

1617
## [2.17.0] - 2021-09-27
1718
### Added

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ lib('generic.EndWithNewlineStep') +'{{yes}} | {{yes}}
4848
lib('generic.IndentStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
4949
lib('generic.Jsr223Step') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
5050
lib('generic.LicenseHeaderStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
51-
lib('generic.NativeCmdStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
51+
lib('generic.NativeCmdStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
5252
lib('generic.ReplaceRegexStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
5353
lib('generic.ReplaceStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
5454
lib('generic.TrimTrailingWhitespaceStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
@@ -66,6 +66,7 @@ lib('kotlin.DiktatStep') +'{{yes}} | {{yes}}
6666
lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
6767
lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
6868
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
69+
lib('pom.SortPomStepStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
6970
lib('python.BlackStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
7071
lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
7172
lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
@@ -86,7 +87,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
8687
| [`generic.IndentStep`](lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
8788
| [`generic.Jsr223Step`](lib/src/main/java/com/diffplug/spotless/generic/Jsr223Step.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
8889
| [`generic.LicenseHeaderStep`](lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
89-
| [`generic.NativeCmdStep`](lib/src/main/java/com/diffplug/spotless/generic/NativeCmdStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
90+
| [`generic.NativeCmdStep`](lib/src/main/java/com/diffplug/spotless/generic/NativeCmdStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
9091
| [`generic.ReplaceRegexStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceRegexStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
9192
| [`generic.ReplaceStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
9293
| [`generic.TrimTrailingWhitespaceStep`](lib/src/main/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
@@ -104,6 +105,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
104105
| [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
105106
| [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
106107
| [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
108+
| [`pom.SortPomStepStep`](lib/src/main/java/com/diffplug/spotless/pom/SortPomStepStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
107109
| [`python.BlackStep`](lib/src/main/java/com/diffplug/spotless/python/BlackStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
108110
| [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: |
109111
| [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: |

lib/build.gradle

+20
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,35 @@ version = rootProject.spotlessChangelog.versionNext
66
apply from: rootProject.file('gradle/java-setup.gradle')
77
apply from: rootProject.file('gradle/java-publish.gradle')
88

9+
def NEEDS_GLUE = [
10+
'sortPom'
11+
]
12+
for (glue in NEEDS_GLUE) {
13+
sourceSets.register(glue) {
14+
compileClasspath += sourceSets.main.output
15+
runtimeClasspath += sourceSets.main.output
16+
java {}
17+
}
18+
}
19+
920
dependencies {
1021
// zero runtime reqs is a hard requirements for spotless-lib
1122
// if you need a dep, put it in lib-extra
1223
testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}"
1324
testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}"
1425
testImplementation "com.diffplug.durian:durian-testlib:${VER_DURIAN}"
26+
27+
// used for pom sorting
28+
sortPomCompileOnly 'com.github.ekryd.sortpom:sortpom-sorter:3.0.0'
1529
}
1630

1731
// we'll hold the core lib to a high standard
1832
spotbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes)
1933

2034
test { useJUnitPlatform() }
35+
36+
jar {
37+
for (glue in NEEDS_GLUE) {
38+
from sourceSets.getByName(glue).output.classesDirs
39+
}
40+
}

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

+60-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 DiffPlug
2+
* Copyright 2016-2021 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.
@@ -15,11 +15,13 @@
1515
*/
1616
package com.diffplug.spotless;
1717

18+
import java.io.ByteArrayOutputStream;
19+
import java.io.IOException;
20+
import java.io.InputStream;
1821
import java.net.URL;
1922
import java.net.URLClassLoader;
20-
import java.util.Arrays;
21-
import java.util.Collections;
22-
import java.util.List;
23+
import java.nio.ByteBuffer;
24+
import java.security.ProtectionDomain;
2325
import java.util.Objects;
2426

2527
import javax.annotation.Nullable;
@@ -29,37 +31,31 @@
2931
* path of URLs.<br/>
3032
* Features shall be independent from build tools. Hence the class loader of the
3133
* underlying build tool is e.g. skipped during the the search for classes.<br/>
32-
* Only {@link #BUILD_TOOLS_PACKAGES } are explicitly looked up from the class loader of
33-
* the build tool and the provided URLs are ignored. This allows the feature to use
34-
* distinct functionality of the build tool.
34+
*
35+
* For `com.diffplug.spotless.glue.`, classes are redefined from within the lib jar
36+
* but linked against the `Url[]`. This allows us to ship classfiles which function as glue
37+
* code but delay linking/definition to runtime after the user has specified which version
38+
* of the formatter they want.
39+
*
40+
* For `"org.slf4j.` and (`com.diffplug.spotless.` but not `com.diffplug.spotless.extra.`)
41+
* the classes are loaded from the buildToolClassLoader.
3542
*/
3643
class FeatureClassLoader extends URLClassLoader {
3744
static {
3845
ClassLoader.registerAsParallelCapable();
3946
}
4047

41-
/**
42-
* The following packages must be provided by the build tool or the corresponding Spotless plugin:
43-
* <ul>
44-
* <li>org.slf4j - SLF4J API must be provided. If no SLF4J binding is provided, log messages are dropped.</li>
45-
* </ul>
46-
*/
47-
static final List<String> BUILD_TOOLS_PACKAGES = Collections.unmodifiableList(Arrays.asList("org.slf4j."));
48-
// NOTE: if this changes, you need to also update the `JarState.getClassLoader` methods.
49-
5048
private final ClassLoader buildToolClassLoader;
5149

5250
/**
5351
* Constructs a new FeatureClassLoader for the given URLs, based on an {@code URLClassLoader},
54-
* using the system class loader as parent. For {@link #BUILD_TOOLS_PACKAGES }, the build
55-
* tool class loader is used.
52+
* using the system class loader as parent.
5653
*
5754
* @param urls the URLs from which to load classes and resources
5855
* @param buildToolClassLoader The build tool class loader
5956
* @exception SecurityException If a security manager exists and prevents the creation of a class loader.
6057
* @exception NullPointerException if {@code urls} is {@code null}.
6158
*/
62-
6359
FeatureClassLoader(URL[] urls, ClassLoader buildToolClassLoader) {
6460
super(urls, getParentClassLoader());
6561
Objects.requireNonNull(buildToolClassLoader);
@@ -68,12 +64,53 @@ class FeatureClassLoader extends URLClassLoader {
6864

6965
@Override
7066
protected Class<?> findClass(String name) throws ClassNotFoundException {
71-
for (String buildToolPackage : BUILD_TOOLS_PACKAGES) {
72-
if (name.startsWith(buildToolPackage)) {
73-
return buildToolClassLoader.loadClass(name);
67+
if (name.startsWith("com.diffplug.spotless.glue.")) {
68+
String path = name.replace('.', '/') + ".class";
69+
URL url = findResource(path);
70+
if (url == null) {
71+
throw new ClassNotFoundException(name);
72+
}
73+
try {
74+
return defineClass(name, urlToByteBuffer(url), (ProtectionDomain) null);
75+
} catch (IOException e) {
76+
throw new ClassNotFoundException(name, e);
7477
}
78+
} else if (useBuildToolClassLoader(name)) {
79+
return buildToolClassLoader.loadClass(name);
80+
} else {
81+
return super.findClass(name);
82+
}
83+
}
84+
85+
private static boolean useBuildToolClassLoader(String name) {
86+
if (name.startsWith("org.slf4j.")) {
87+
return true;
88+
} else if (!name.startsWith("com.diffplug.spotless.extra") && name.startsWith("com.diffplug.spotless.")) {
89+
return true;
90+
} else {
91+
return false;
92+
}
93+
}
94+
95+
@Override
96+
public URL findResource(String name) {
97+
URL resource = super.findResource(name);
98+
if (resource != null) {
99+
return resource;
100+
}
101+
return buildToolClassLoader.getResource(name);
102+
}
103+
104+
private static ByteBuffer urlToByteBuffer(URL url) throws IOException {
105+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
106+
int nRead;
107+
byte[] data = new byte[1024];
108+
InputStream inputStream = url.openStream();
109+
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
110+
buffer.write(data, 0, nRead);
75111
}
76-
return super.findClass(name);
112+
buffer.flush();
113+
return ByteBuffer.wrap(buffer.toByteArray());
77114
}
78115

79116
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2021 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.pom;
17+
18+
import java.io.Serializable;
19+
20+
// Class and members must be public, otherwise we get failed to access class com.diffplug.spotless.pom.SortPomInternalState from class com.diffplug.spotless.pom.SortPomFormatterFunc (com.diffplug.spotless.pom.SortPomInternalState is in unnamed module of loader org.codehaus.plexus.classworlds.realm.ClassRealm @682bd3c4; com.diffplug.spotless.pom.SortPomFormatterFunc is in unnamed module of loader com.diffplug.spotless.pom.DelegatingClassLoader @573284a5)
21+
public class SortPomCfg implements Serializable {
22+
private static final long serialVersionUID = 1L;
23+
24+
public String encoding = "UTF-8";
25+
26+
public String lineSeparator = System.getProperty("line.separator");
27+
28+
public boolean expandEmptyElements = true;
29+
30+
public boolean spaceBeforeCloseEmptyElement = false;
31+
32+
public boolean keepBlankLines = true;
33+
34+
public int nrOfIndentSpace = 2;
35+
36+
public boolean indentBlankLines = false;
37+
38+
public boolean indentSchemaLocation = false;
39+
40+
public String predefinedSortOrder = "recommended_2008_06";
41+
42+
public String sortOrderFile = null;
43+
44+
public String sortDependencies = null;
45+
46+
public String sortDependencyExclusions = null;
47+
48+
public String sortPlugins = null;
49+
50+
public boolean sortProperties = false;
51+
52+
public boolean sortModules = false;
53+
54+
public boolean sortExecutions = false;
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2021 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.pom;
17+
18+
import java.io.IOException;
19+
import java.io.Serializable;
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.InvocationTargetException;
22+
23+
import com.diffplug.spotless.FormatterFunc;
24+
import com.diffplug.spotless.FormatterStep;
25+
import com.diffplug.spotless.JarState;
26+
import com.diffplug.spotless.Provisioner;
27+
28+
public class SortPomStep {
29+
public static final String NAME = "sortPom";
30+
31+
private SortPomStep() {}
32+
33+
private SortPomCfg cfg;
34+
35+
public static FormatterStep create(SortPomCfg cfg, Provisioner provisioner) {
36+
return FormatterStep.createLazy(NAME, () -> new State(cfg, provisioner), State::createFormat);
37+
}
38+
39+
static class State implements Serializable {
40+
SortPomCfg cfg;
41+
JarState jarState;
42+
43+
public State(SortPomCfg cfg, Provisioner provisioner) throws IOException {
44+
this.cfg = cfg;
45+
this.jarState = JarState.from("com.github.ekryd.sortpom:sortpom-sorter:3.0.0", provisioner);
46+
}
47+
48+
FormatterFunc createFormat() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
49+
Class<?> formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.pom.SortPomFormatterFunc");
50+
Constructor<?> constructor = formatterFunc.getConstructor(SortPomCfg.class);
51+
return (FormatterFunc) constructor.newInstance(cfg);
52+
}
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2021 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.glue.pom;
17+
18+
import java.io.File;
19+
import java.io.FileInputStream;
20+
import java.io.FileOutputStream;
21+
import java.util.logging.Logger;
22+
23+
import org.apache.commons.io.IOUtils;
24+
25+
import com.diffplug.spotless.FormatterFunc;
26+
import com.diffplug.spotless.pom.SortPomCfg;
27+
28+
import sortpom.SortPomImpl;
29+
import sortpom.logger.SortPomLogger;
30+
import sortpom.parameter.PluginParameters;
31+
32+
public class SortPomFormatterFunc implements FormatterFunc {
33+
private static final Logger logger = Logger.getLogger(SortPomFormatterFunc.class.getName());
34+
private final SortPomCfg cfg;
35+
36+
public SortPomFormatterFunc(SortPomCfg cfg) {
37+
this.cfg = cfg;
38+
}
39+
40+
@Override
41+
public String apply(String input) throws Exception {
42+
// SortPom expects a file to sort, so we write the inpout into a temporary file
43+
File pom = File.createTempFile("pom", ".xml");
44+
pom.deleteOnExit();
45+
IOUtils.write(input, new FileOutputStream(pom), cfg.encoding);
46+
SortPomImpl sortPom = new SortPomImpl();
47+
sortPom.setup(new MySortPomLogger(), PluginParameters.builder()
48+
.setPomFile(pom)
49+
.setFileOutput(false, null, null, false)
50+
.setEncoding(cfg.encoding)
51+
.setFormatting(cfg.lineSeparator, cfg.expandEmptyElements, cfg.spaceBeforeCloseEmptyElement, cfg.keepBlankLines)
52+
.setIndent(cfg.nrOfIndentSpace, cfg.indentBlankLines, cfg.indentSchemaLocation)
53+
.setSortOrder(cfg.sortOrderFile, cfg.predefinedSortOrder)
54+
.setSortEntities(cfg.sortDependencies, cfg.sortDependencyExclusions, cfg.sortPlugins, cfg.sortProperties, cfg.sortModules, cfg.sortExecutions)
55+
.setTriggers(false)
56+
.build());
57+
sortPom.sortPom();
58+
return IOUtils.toString(new FileInputStream(pom), cfg.encoding);
59+
}
60+
61+
private static class MySortPomLogger implements SortPomLogger {
62+
@Override
63+
public void warn(String content) {
64+
logger.warning(content);
65+
}
66+
67+
@Override
68+
public void info(String content) {
69+
logger.info(content);
70+
}
71+
72+
@Override
73+
public void error(String content) {
74+
logger.severe(content);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)