Skip to content

Commit a52fa2f

Browse files
authored
Migrate FenceStep and the toggleOffOn to support ConfigurationCacheHackList (#2378 fixes #2317)
2 parents 55a7db4 + 99a32ff commit a52fa2f

File tree

12 files changed

+209
-112
lines changed

12 files changed

+209
-112
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
3434
### Changed
3535
* **BREAKING** Moved `PaddedCell.DirtyState` to its own top-level class with new methods. ([#2148](https://github.com/diffplug/spotless/pull/2148))
3636
* **BREAKING** Removed `isClean`, `applyTo`, and `applyToAndReturnResultIfDirty` from `Formatter` because users should instead use `DirtyState`.
37+
* `FenceStep` now uses `ConfigurationCacheHack`. ([#2378](https://github.com/diffplug/spotless/pull/2378) fixes [#2317](https://github.com/diffplug/spotless/issues/2317))
3738
### Fixed
3839
* `ktlint` steps now read from the `string` instead of the `file` so they don't clobber earlier steps. (fixes [#1599](https://github.com/diffplug/spotless/issues/1599))
3940

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

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

18+
import java.io.IOException;
19+
import java.io.Serializable;
1820
import java.util.ArrayList;
1921
import java.util.Collection;
2022
import java.util.List;
2123
import java.util.Objects;
2224

25+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26+
2327
/**
2428
* Gradle requires three things:
2529
* - Gradle defines cache equality based on your serialized representation
@@ -48,8 +52,28 @@
4852
*/
4953
public class ConfigurationCacheHackList implements java.io.Serializable {
5054
private static final long serialVersionUID = 1L;
51-
private final boolean optimizeForEquality;
52-
private final ArrayList<Object> backingList = new ArrayList<>();
55+
private boolean optimizeForEquality;
56+
private ArrayList<Object> backingList = new ArrayList<>();
57+
58+
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
59+
out.writeBoolean(optimizeForEquality);
60+
out.writeInt(backingList.size());
61+
for (Object obj : backingList) {
62+
// if write out the list on its own, we'll get java's non-deterministic object-graph serialization
63+
// by writing each object to raw bytes independently, we avoid this
64+
out.writeObject(LazyForwardingEquality.toBytes((Serializable) obj));
65+
}
66+
}
67+
68+
@SuppressFBWarnings("MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT")
69+
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
70+
optimizeForEquality = in.readBoolean();
71+
backingList = new ArrayList<>();
72+
int size = in.readInt();
73+
for (int i = 0; i < size; i++) {
74+
backingList.add(LazyForwardingEquality.fromBytes((byte[]) in.readObject()));
75+
}
76+
}
5377

5478
public static ConfigurationCacheHackList forEquality() {
5579
return new ConfigurationCacheHackList(true);

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

+3-3
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.
@@ -106,7 +106,7 @@ static <RoundtripState extends Serializable, EqualityState extends Serializable>
106106
String name,
107107
ThrowingEx.Supplier<RoundtripState> roundtripInit,
108108
SerializedFunction<RoundtripState, EqualityState> equalityFunc,
109-
SerializedFunction<EqualityState, FormatterFunc> formatterFunc) {
109+
SerializedFunction<EqualityState, ? extends FormatterFunc> formatterFunc) {
110110
return new FormatterStepSerializationRoundtrip<>(name, roundtripInit, equalityFunc, formatterFunc);
111111
}
112112

@@ -128,7 +128,7 @@ static <RoundtripState extends Serializable, EqualityState extends Serializable>
128128
String name,
129129
RoundtripState roundTrip,
130130
SerializedFunction<RoundtripState, EqualityState> equalityFunc,
131-
SerializedFunction<EqualityState, FormatterFunc> formatterFunc) {
131+
SerializedFunction<EqualityState, ? extends FormatterFunc> formatterFunc) {
132132
return createLazy(name, () -> roundTrip, equalityFunc, formatterFunc);
133133
}
134134

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

+3-3
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.
@@ -30,9 +30,9 @@ final class FormatterStepSerializationRoundtrip<RoundtripState extends Serializa
3030
private @Nullable RoundtripState roundtripStateInternal;
3131
private @Nullable EqualityState equalityStateInternal;
3232
private final SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor;
33-
private final SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter;
33+
private final SerializedFunction<EqualityState, ? extends FormatterFunc> equalityStateToFormatter;
3434

35-
FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier<RoundtripState> initializer, SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor, SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter) {
35+
FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier<RoundtripState> initializer, SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor, SerializedFunction<EqualityState, ? extends FormatterFunc> equalityStateToFormatter) {
3636
this.name = name;
3737
this.initializer = initializer;
3838
this.equalityStateExtractor = equalityStateExtractor;

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

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

18+
import java.io.ByteArrayInputStream;
1819
import java.io.ByteArrayOutputStream;
1920
import java.io.IOException;
2021
import java.io.ObjectInputStream;
@@ -112,6 +113,15 @@ static byte[] toBytes(Serializable obj) {
112113
return byteOutput.toByteArray();
113114
}
114115

116+
static Object fromBytes(byte[] bytes) {
117+
ByteArrayInputStream byteOutput = new ByteArrayInputStream(bytes);
118+
try (ObjectInputStream objectOutput = new ObjectInputStream(byteOutput)) {
119+
return objectOutput.readObject();
120+
} catch (IOException | ClassNotFoundException e) {
121+
throw ThrowingEx.asRuntime(e);
122+
}
123+
}
124+
115125
/** Ensures that the lazy state has been evaluated. */
116126
public static void unlazy(Object in) {
117127
if (in instanceof LazyForwardingEquality) {

lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java

+69-88
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2024 DiffPlug
2+
* Copyright 2020-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.
@@ -24,9 +24,9 @@
2424
import java.util.regex.Matcher;
2525
import java.util.regex.Pattern;
2626

27-
import javax.annotation.Nullable;
28-
27+
import com.diffplug.spotless.ConfigurationCacheHackList;
2928
import com.diffplug.spotless.Formatter;
29+
import com.diffplug.spotless.FormatterFunc;
3030
import com.diffplug.spotless.FormatterStep;
3131
import com.diffplug.spotless.LineEnding;
3232
import com.diffplug.spotless.Lint;
@@ -80,94 +80,83 @@ private void assertRegexSet() {
8080

8181
/** Returns a step which will apply the given steps but preserve the content selected by the regex / openClose pair. */
8282
public FormatterStep preserveWithin(List<FormatterStep> steps) {
83-
assertRegexSet();
84-
return new PreserveWithin(name, regex, steps);
83+
return createStep(Kind.PRESERVE, steps);
8584
}
8685

8786
/**
8887
* Returns a step which will apply the given steps only within the blocks selected by the regex / openClose pair.
8988
* Linting within the substeps is not supported.
9089
*/
9190
public FormatterStep applyWithin(List<FormatterStep> steps) {
91+
return createStep(Kind.APPLY, steps);
92+
}
93+
94+
private FormatterStep createStep(Kind kind, List<FormatterStep> steps) {
9295
assertRegexSet();
93-
return new ApplyWithin(name, regex, steps);
96+
return FormatterStep.createLazy(name, () -> new RoundtripAndEqualityState(kind, regex, steps, false),
97+
RoundtripAndEqualityState::toEqualityState,
98+
RoundtripAndEqualityState::toFormatterFunc);
9499
}
95100

96-
static class ApplyWithin extends BaseStep {
97-
private static final long serialVersionUID = 17061466531957339L;
101+
private enum Kind {
102+
APPLY, PRESERVE
103+
}
98104

99-
ApplyWithin(String name, Pattern regex, List<FormatterStep> steps) {
100-
super(name, regex, steps);
101-
}
105+
private static class RoundtripAndEqualityState implements Serializable {
106+
private static final long serialVersionUID = 272603249547598947L;
107+
final String regexPattern;
108+
final int regexFlags;
109+
final Kind kind;
110+
final ConfigurationCacheHackList steps;
102111

103-
@Override
104-
protected String applySubclass(Formatter formatter, String unix, File file) {
105-
List<String> groups = groupsZeroed();
106-
Matcher matcher = regex.matcher(unix);
107-
while (matcher.find()) {
108-
// apply the formatter to each group
109-
groups.add(formatter.compute(matcher.group(1), file));
110-
}
111-
// and then assemble the result right away
112-
return assembleGroups(unix);
112+
/** Roundtrip state. */
113+
private RoundtripAndEqualityState(Kind kind, Pattern regex, List<FormatterStep> steps, boolean optimizeForEquality) {
114+
this.kind = kind;
115+
this.regexPattern = regex.pattern();
116+
this.regexFlags = regex.flags();
117+
this.steps = optimizeForEquality ? ConfigurationCacheHackList.forEquality() : ConfigurationCacheHackList.forRoundtrip();
118+
this.steps.addAll(steps);
113119
}
114-
}
115120

116-
static class PreserveWithin extends BaseStep {
117-
private static final long serialVersionUID = -8676786492305178343L;
121+
private Pattern regex() {
122+
return Pattern.compile(regexPattern, regexFlags);
123+
}
118124

119-
PreserveWithin(String name, Pattern regex, List<FormatterStep> steps) {
120-
super(name, regex, steps);
125+
private List<FormatterStep> steps() {
126+
return steps.getSteps();
121127
}
122128

123-
private void storeGroups(String unix) {
124-
List<String> groups = groupsZeroed();
125-
Matcher matcher = regex.matcher(unix);
126-
while (matcher.find()) {
127-
// store whatever is within the open/close tags
128-
groups.add(matcher.group(1));
129-
}
129+
public RoundtripAndEqualityState toEqualityState() {
130+
return new RoundtripAndEqualityState(kind, regex(), steps(), true);
130131
}
131132

132-
@Override
133-
protected String applySubclass(Formatter formatter, String unix, File file) {
134-
storeGroups(unix);
135-
String formatted = formatter.compute(unix, file);
136-
return assembleGroups(formatted);
133+
public BaseFormatter toFormatterFunc() {
134+
return new BaseFormatter(kind, this);
137135
}
138136
}
139137

140138
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
141-
private static abstract class BaseStep implements Serializable, FormatterStep {
142-
final String name;
143-
private static final long serialVersionUID = -2301848328356559915L;
139+
private static class BaseFormatter implements FormatterFunc.NeedsFile, FormatterFunc.Closeable {
140+
final Kind kind;
144141
final Pattern regex;
145142
final List<FormatterStep> steps;
146143

147-
transient ArrayList<String> groups = new ArrayList<>();
148-
transient StringBuilder builderInternal;
144+
final ArrayList<String> groups = new ArrayList<>();
145+
final StringBuilder builderInternal = new StringBuilder();
149146

150-
public BaseStep(String name, Pattern regex, List<FormatterStep> steps) {
151-
this.name = name;
152-
this.regex = regex;
153-
this.steps = steps;
147+
public BaseFormatter(Kind kind, RoundtripAndEqualityState state) {
148+
this.kind = kind;
149+
this.regex = state.regex();
150+
this.steps = state.steps();
154151
}
155152

156153
protected ArrayList<String> groupsZeroed() {
157-
if (groups == null) {
158-
groups = new ArrayList<>();
159-
} else {
160-
groups.clear();
161-
}
154+
groups.clear();
162155
return groups;
163156
}
164157

165158
private StringBuilder builderZeroed() {
166-
if (builderInternal == null) {
167-
builderInternal = new StringBuilder();
168-
} else {
169-
builderInternal.setLength(0);
170-
}
159+
builderInternal.setLength(0);
171160
return builderInternal;
172161
}
173162

@@ -215,41 +204,33 @@ protected String assembleGroups(String unix) {
215204
}
216205
}
217206

218-
@Override
219-
public String getName() {
220-
return name;
221-
}
207+
private Formatter formatter;
222208

223-
private transient Formatter formatter;
224-
225-
private String apply(String rawUnix, File file) throws Exception {
209+
@Override
210+
public String applyWithFile(String unix, File file) throws Exception {
226211
if (formatter == null) {
227212
formatter = buildFormatter();
228213
}
229-
return applySubclass(formatter, rawUnix, file);
230-
}
231-
232-
@Nullable
233-
@Override
234-
public String format(String rawUnix, File file) throws Exception {
235-
return apply(rawUnix, file);
236-
}
237-
238-
protected abstract String applySubclass(Formatter formatter, String unix, File file) throws Exception;
239-
240-
@Override
241-
public boolean equals(Object o) {
242-
if (this == o)
243-
return true;
244-
if (o == null || getClass() != o.getClass())
245-
return false;
246-
BaseStep step = (BaseStep) o;
247-
return name.equals(step.name) && regex.pattern().equals(step.regex.pattern()) && regex.flags() == step.regex.flags() && steps.equals(step.steps);
248-
}
249-
250-
@Override
251-
public int hashCode() {
252-
return Objects.hash(name, regex.pattern(), regex.flags(), steps);
214+
List<String> groups = groupsZeroed();
215+
Matcher matcher = regex.matcher(unix);
216+
switch (kind) {
217+
case APPLY:
218+
while (matcher.find()) {
219+
// apply the formatter to each group
220+
groups.add(formatter.compute(matcher.group(1), file));
221+
}
222+
// and then assemble the result right away
223+
return assembleGroups(unix);
224+
case PRESERVE:
225+
while (matcher.find()) {
226+
// store whatever is within the open/close tags
227+
groups.add(matcher.group(1));
228+
}
229+
String formatted = formatter.compute(unix, file);
230+
return assembleGroups(formatted);
231+
default:
232+
throw new Error();
233+
}
253234
}
254235

255236
@Override

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

+3-3
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.
@@ -105,12 +105,12 @@ private static FormatterStep createInternally(String name, String groupArtifact,
105105

106106
GoogleJavaFormatStep step = new GoogleJavaFormatStep(JarState.promise(() -> JarState.from(groupArtifact + ":" + version, provisioner)), version, style, reflowLongStrings, reorderImports, formatJavadoc);
107107
if (removeImports) {
108-
return FormatterStep.create(NAME,
108+
return FormatterStep.create(name,
109109
step,
110110
GoogleJavaFormatStep::equalityState,
111111
State::createRemoveUnusedImportsOnly);
112112
} else {
113-
return FormatterStep.create(NAME,
113+
return FormatterStep.create(name,
114114
step,
115115
GoogleJavaFormatStep::equalityState,
116116
State::createFormat);

plugin-gradle/CHANGES.md

+1
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
* Bump default `ktlint` version to latest `1.4.0` -> `1.5.0`. ([#2354](https://github.com/diffplug/spotless/pull/2354))
1212
* Bump minimum `eclipse-cdt` version to `11.0` (removed support for `10.7`). ([#2373](https://github.com/diffplug/spotless/pull/2373))
1313
### Fixed
14+
* `toggleOffOn` now works with the configuration cache. ([#2378](https://github.com/diffplug/spotless/pull/2378) fixes [#2317](https://github.com/diffplug/spotless/issues/2317))
1415
* You can now use `removeUnusedImports` and `googleJavaFormat` at the same time again. (fixes [#2159](https://github.com/diffplug/spotless/issues/2159))
1516
* The default list of type annotations used by `formatAnnotations` now includes Jakarta Validation's `Valid` and constraints validations (fixes [#2334](https://github.com/diffplug/spotless/issues/2334))
1617
* `indentWith[Spaces|Tabs]` has been deprecated in favor of `leadingTabsToSpaces` and `leadingSpacesToTabs`. ([#2350](https://github.com/diffplug/spotless/pull/2350) fixes [#794](https://github.com/diffplug/spotless/issues/794))

0 commit comments

Comments
 (0)