From f519ed36a2c5ad5d90976e9b93697b2fe5dde914 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 20 Feb 2025 13:31:55 +0100 Subject: [PATCH 1/4] feat: allow overriding classLoader for jarstate Opening up usage of spotless-lib in `spotless-cli` --- .../java/com/diffplug/spotless/JarState.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index 8680932b9e..76dee4f438 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -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. @@ -28,6 +28,11 @@ import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Grabs a jar and its dependencies from maven, * and makes it easy to access the collection in @@ -37,6 +42,21 @@ * catch changes in a SNAPSHOT version. */ public final class JarState implements Serializable { + + private static final Logger logger = LoggerFactory.getLogger(JarState.class); + + // Let the classloader be overridden for tools using different approaches to classloading + @Nullable + private static ClassLoader forcedClassLoader = null; + + /** Overrides the classloader used by all JarStates. */ + public static void setForcedClassLoader(@Nullable ClassLoader forcedClassLoader) { + if (!Objects.equals(JarState.forcedClassLoader, forcedClassLoader)) { + logger.info("Overriding the forced classloader for JarState from {} to {}", JarState.forcedClassLoader, forcedClassLoader); + } + JarState.forcedClassLoader = forcedClassLoader; + } + /** A lazily evaluated JarState, which becomes a set of files when serialized. */ public static class Promised implements Serializable { private static final long serialVersionUID = 1L; @@ -125,26 +145,36 @@ URL[] jarUrls() { } /** - * Returns a classloader containing the only jars in this JarState. + * Returns either a forcedClassloader ({@code JarState.setForcedClassLoader()}) or a classloader containing the only jars in this JarState. * Look-up of classes in the {@code org.slf4j} package * are not taken from the JarState, but instead redirected to the class loader of this class to enable * passthrough logging. *
* The lifetime of the underlying cacheloader is controlled by {@link SpotlessCache}. + * + * @see com.diffplug.spotless.JarState#setForcedClassLoader(ClassLoader) */ public ClassLoader getClassLoader() { + if (forcedClassLoader != null) { + return forcedClassLoader; + } return SpotlessCache.instance().classloader(this); } /** - * Returns a classloader containing the only jars in this JarState. + * Returns either a forcedClassloader ({@code JarState.setForcedClassLoader}) or a classloader containing the only jars in this JarState. * Look-up of classes in the {@code org.slf4j} package * are not taken from the JarState, but instead redirected to the class loader of this class to enable * passthrough logging. *
- * The lifetime of the underlying cacheloader is controlled by {@link SpotlessCache}. + * The lifetime of the underlying cacheloader is controlled by {@link SpotlessCache} + * + * @see com.diffplug.spotless.JarState#setForcedClassLoader(ClassLoader) */ public ClassLoader getClassLoader(Serializable key) { + if (forcedClassLoader != null) { + return forcedClassLoader; + } return SpotlessCache.instance().classloader(key, this); } } From 88d3c318a06a48e20e5d843930a638ff75ae7a27 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 20 Feb 2025 13:35:02 +0100 Subject: [PATCH 2/4] chore: update changelog for reflecting overridable classLoader in JarState --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index b170b7086d..d7abd321fa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,7 +10,9 @@ This document is intended for Spotless developers. We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Added * Support for`clang-format` on maven-plugin ([#2406](https://github.com/diffplug/spotless/pull/2406)) +* Allow overriding classLoader for all `JarState`s to enable spotless-cli ([#xxx](https://github.com/diffplug/spotless/pull/XXX)) ## [3.0.2] - 2025-01-14 ### Fixed From 8ee1dfe45e3ca426ed82376c61be5f7af7144352 Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 20 Feb 2025 16:16:12 +0100 Subject: [PATCH 3/4] chore: provide test to make sure overriding classloader works --- .../com/diffplug/spotless/JarStateTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 lib/src/test/java/com/diffplug/spotless/JarStateTest.java diff --git a/lib/src/test/java/com/diffplug/spotless/JarStateTest.java b/lib/src/test/java/com/diffplug/spotless/JarStateTest.java new file mode 100644 index 0000000000..f44aa4a0b3 --- /dev/null +++ b/lib/src/test/java/com/diffplug/spotless/JarStateTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2025 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.stream.Collectors; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class JarStateTest { + + @TempDir + java.nio.file.Path tempDir; + + File a; + + File b; + + Provisioner provisioner = (withTransitives, deps) -> deps.stream().map(name -> name.equals("a") ? a : b).collect(Collectors.toSet()); + + @BeforeEach + void setUp() throws IOException { + a = Files.createTempFile(tempDir, "a", ".class").toFile(); + Files.writeString(a.toPath(), "a"); + b = Files.createTempFile(tempDir, "b", ".class").toFile(); + Files.writeString(b.toPath(), "b"); + } + + @AfterEach + void tearDown() { + JarState.setForcedClassLoader(null); + } + + @Test + void itCreatesClassloaderWhenForcedClassLoaderNotSet() throws IOException { + JarState state1 = JarState.from(a.getName(), provisioner); + JarState state2 = JarState.from(b.getName(), provisioner); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(state1.getClassLoader()).isNotNull(); + softly.assertThat(state2.getClassLoader()).isNotNull(); + }); + } + + @Test + void itReturnsForcedClassloaderIfSetNoMatterIfSetBeforeOrAfterCreation() throws IOException { + JarState stateA = JarState.from(a.getName(), provisioner); + ClassLoader forcedClassLoader = new URLClassLoader(new java.net.URL[0]); + JarState.setForcedClassLoader(forcedClassLoader); + JarState stateB = JarState.from(b.getName(), provisioner); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(stateA.getClassLoader()).isSameAs(forcedClassLoader); + softly.assertThat(stateB.getClassLoader()).isSameAs(forcedClassLoader); + }); + } + + @Test + void itReturnsForcedClassloaderEvenWhenRountripSerialized() throws IOException, ClassNotFoundException { + JarState stateA = JarState.from(a.getName(), provisioner); + ClassLoader forcedClassLoader = new URLClassLoader(new java.net.URL[0]); + JarState.setForcedClassLoader(forcedClassLoader); + JarState stateB = JarState.from(b.getName(), provisioner); + + JarState stateARoundtripSerialized = roundtripSerialize(stateA); + JarState stateBRoundtripSerialized = roundtripSerialize(stateB); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(stateARoundtripSerialized.getClassLoader()).isSameAs(forcedClassLoader); + softly.assertThat(stateBRoundtripSerialized.getClassLoader()).isSameAs(forcedClassLoader); + }); + } + + private JarState roundtripSerialize(JarState state) throws IOException, ClassNotFoundException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (ObjectOutputStream oOut = new ObjectOutputStream(outputStream)) { + oOut.writeObject(state); + } + try (ObjectInputStream oIn = new ObjectInputStream(new java.io.ByteArrayInputStream(outputStream.toByteArray()))) { + return (JarState) oIn.readObject(); + } + } + +} From 06c6ca8ba332472c41a92dffcc2b436b3d4b5a6e Mon Sep 17 00:00:00 2001 From: Simon Gamma Date: Thu, 20 Feb 2025 16:19:34 +0100 Subject: [PATCH 4/4] chore: insert created PR# --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d7abd321fa..6e706fc8e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added * Support for`clang-format` on maven-plugin ([#2406](https://github.com/diffplug/spotless/pull/2406)) -* Allow overriding classLoader for all `JarState`s to enable spotless-cli ([#xxx](https://github.com/diffplug/spotless/pull/XXX)) +* Allow overriding classLoader for all `JarState`s to enable spotless-cli ([#2427](https://github.com/diffplug/spotless/pull/2427)) ## [3.0.2] - 2025-01-14 ### Fixed