Skip to content

Commit 716722a

Browse files
committed
Give FormatterStepSerializationRoundtrip a special serialization format instead.
1 parent 9881ea4 commit 716722a

File tree

4 files changed

+109
-64
lines changed

4 files changed

+109
-64
lines changed

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

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,73 +15,89 @@
1515
*/
1616
package com.diffplug.spotless;
1717

18-
import java.io.IOException;
19-
import java.util.AbstractList;
2018
import java.util.ArrayList;
2119
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.Objects;
2222

2323
/**
2424
* Gradle requires three things:
2525
* - Gradle defines cache equality based on your serialized representation
26-
* - Combined with remote build cache, you cannot have any absolute paths in your serialized representation
27-
* - Combined with configuration cache, you must be able to roundtrip yourself through serialization
26+
* - Combined with remote build cache, you cannot have any absolute paths in
27+
* your serialized representation
28+
* - Combined with configuration cache, you must be able to roundtrip yourself
29+
* through serialization
2830
*
2931
* These requirements are at odds with each other, as described in these issues
30-
* - Gradle issue to define custom equality https://github.com/gradle/gradle/issues/29816
31-
* - Spotless plea for developer cache instead of configuration cache https://github.com/diffplug/spotless/issues/987
32-
* - Spotless cache miss bug fixed by this class https://github.com/diffplug/spotless/issues/2168
32+
* - Gradle issue to define custom equality
33+
* https://github.com/gradle/gradle/issues/29816
34+
* - Spotless plea for developer cache instead of configuration cache
35+
* https://github.com/diffplug/spotless/issues/987
36+
* - Spotless cache miss bug fixed by this class
37+
* https://github.com/diffplug/spotless/issues/2168
3338
*
34-
* The point of this class is to create containers which can optimize the serialized representation for either
39+
* The point of this class is to create containers which can optimize the
40+
* serialized representation for either
3541
* - roundtrip integrity
3642
* - equality
3743
*
3844
* It is a horrific hack, but it works, and it's the only way I can figure
3945
* to make Spotless work with all of Gradle's cache systems.
4046
*/
4147
public class ConfigurationCacheHack {
42-
static boolean SERIALIZE_FOR_ROUNDTRIP = false;
43-
4448
public enum OptimizeFor {
4549
ROUNDTRIP, EQUALITY,
4650
}
4751

48-
public static class StepList extends AbstractList<FormatterStep> {
52+
public static class StepList implements java.io.Serializable {
4953
private final boolean optimizeForEquality;
50-
private transient ArrayList<FormatterStep> backingList = new ArrayList<>();
54+
private final ArrayList<Object> backingList = new ArrayList<>();
5155

5256
public StepList(OptimizeFor optimizeFor) {
5357
this.optimizeForEquality = optimizeFor == OptimizeFor.EQUALITY;
5458
}
5559

56-
@Override
5760
public void clear() {
5861
backingList.clear();
5962
}
6063

61-
@Override
62-
public boolean addAll(Collection<? extends FormatterStep> c) {
63-
return backingList.addAll(c);
64-
}
65-
66-
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
67-
out.defaultWriteObject();
68-
SERIALIZE_FOR_ROUNDTRIP = !optimizeForEquality;
69-
out.writeObject(backingList);
64+
public void addAll(Collection<? extends FormatterStep> c) {
65+
for (FormatterStep step : c) {
66+
if (step instanceof FormatterStepSerializationRoundtrip) {
67+
var clone = ((FormatterStepSerializationRoundtrip) step).hackClone(optimizeForEquality);
68+
backingList.add(clone);
69+
} else {
70+
backingList.add(step);
71+
}
72+
}
7073
}
7174

72-
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
73-
in.defaultReadObject();
74-
backingList = (ArrayList<FormatterStep>) in.readObject();
75+
public List<FormatterStep> getSteps() {
76+
var result = new ArrayList<FormatterStep>(backingList.size());
77+
for (Object obj : backingList) {
78+
if (obj instanceof FormatterStepSerializationRoundtrip.HackClone) {
79+
result.add(((FormatterStepSerializationRoundtrip.HackClone) obj).rehydrate());
80+
} else {
81+
result.add((FormatterStep) obj);
82+
}
83+
}
84+
return result;
7585
}
7686

7787
@Override
78-
public FormatterStep get(int index) {
79-
return backingList.get(index);
88+
public boolean equals(Object o) {
89+
if (this == o)
90+
return true;
91+
if (o == null || getClass() != o.getClass())
92+
return false;
93+
StepList stepList = (StepList) o;
94+
return optimizeForEquality == stepList.optimizeForEquality &&
95+
backingList.equals(stepList.backingList);
8096
}
8197

8298
@Override
83-
public int size() {
84-
return backingList.size();
99+
public int hashCode() {
100+
return Objects.hash(optimizeForEquality, backingList);
85101
}
86102
}
87103
}

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535

3636
import javax.annotation.Nullable;
3737

38-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
39-
4038
/** Formatter which performs the full formatting. */
4139
public final class Formatter implements Serializable, AutoCloseable {
4240
private static final long serialVersionUID = 1L;
@@ -59,13 +57,11 @@ private Formatter(String name, LineEnding.Policy lineEndingsPolicy, Charset enco
5957
}
6058

6159
// override serialize output
62-
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
6360
private void writeObject(ObjectOutputStream out) throws IOException {
6461
out.writeObject(name);
6562
out.writeObject(lineEndingsPolicy);
6663
out.writeObject(encoding.name());
6764
out.writeObject(rootDir.toString());
68-
ConfigurationCacheHack.SERIALIZE_FOR_ROUNDTRIP = true;
6965
out.writeObject(steps);
7066
out.writeObject(exceptionPolicy);
7167
}

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

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
package com.diffplug.spotless;
1717

1818
import java.io.IOException;
19-
import java.io.ObjectStreamException;
2019
import java.io.Serializable;
20+
import java.util.Objects;
2121

2222
import edu.umd.cs.findbugs.annotations.Nullable;
2323

24-
class FormatterStepSerializationRoundtrip<RoundtripState extends Serializable, EqualityState extends Serializable> extends FormatterStepEqualityOnStateSerialization<EqualityState> {
24+
final class FormatterStepSerializationRoundtrip<RoundtripState extends Serializable, EqualityState extends Serializable> extends FormatterStepEqualityOnStateSerialization<EqualityState> {
2525
private static final long serialVersionUID = 1L;
2626
private final String name;
2727
private final transient ThrowingEx.Supplier<RoundtripState> initializer;
@@ -30,7 +30,7 @@ class FormatterStepSerializationRoundtrip<RoundtripState extends Serializable, E
3030
private final SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor;
3131
private final SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter;
3232

33-
public FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier<RoundtripState> initializer, SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor, SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter) {
33+
FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier<RoundtripState> initializer, SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor, SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter) {
3434
this.name = name;
3535
this.initializer = initializer;
3636
this.equalityStateExtractor = equalityStateExtractor;
@@ -42,13 +42,17 @@ public String getName() {
4242
return name;
4343
}
4444

45+
private RoundtripState roundtripStateSupplier() throws Exception {
46+
if (roundtripStateInternal == null) {
47+
roundtripStateInternal = initializer.get();
48+
}
49+
return roundtripStateInternal;
50+
}
51+
4552
@Override
4653
protected EqualityState stateSupplier() throws Exception {
4754
if (equalityStateInternal == null) {
48-
if (roundtripStateInternal == null) {
49-
roundtripStateInternal = initializer.get();
50-
}
51-
equalityStateInternal = equalityStateExtractor.apply(roundtripStateInternal);
55+
equalityStateInternal = equalityStateExtractor.apply(roundtripStateSupplier());
5256
}
5357
return equalityStateInternal;
5458
}
@@ -58,25 +62,55 @@ protected FormatterFunc stateToFormatter(EqualityState equalityState) throws Exc
5862
return equalityStateToFormatter.apply(equalityState);
5963
}
6064

61-
// override serialize output
62-
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
63-
if (ConfigurationCacheHack.SERIALIZE_FOR_ROUNDTRIP) {
64-
if (roundtripStateInternal == null) {
65-
roundtripStateInternal = ThrowingEx.get(initializer::get);
65+
HackClone<?, ?> hackClone(boolean optimizeForEquality) {
66+
return new HackClone<>(this, optimizeForEquality);
67+
}
68+
69+
static class HackClone<RoundtripState extends Serializable, EqualityState extends Serializable> implements Serializable {
70+
transient FormatterStepSerializationRoundtrip<?, ?> original;
71+
boolean optimizeForEquality;
72+
@Nullable
73+
FormatterStepSerializationRoundtrip cleaned;
74+
75+
HackClone(@Nullable FormatterStepSerializationRoundtrip<RoundtripState, EqualityState> original, boolean optimizeForEquality) {
76+
this.original = original;
77+
this.optimizeForEquality = optimizeForEquality;
78+
}
79+
80+
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
81+
if (cleaned == null) {
82+
cleaned = new FormatterStepSerializationRoundtrip(original.name, null, original.equalityStateExtractor, original.equalityStateToFormatter);
83+
if (optimizeForEquality) {
84+
cleaned.equalityStateInternal = ThrowingEx.get(original::stateSupplier);
85+
} else {
86+
cleaned.roundtripStateInternal = ThrowingEx.get(original::roundtripStateSupplier);
87+
}
6688
}
67-
equalityStateInternal = null;
68-
} else {
69-
equalityStateInternal = ThrowingEx.get(this::stateSupplier);
70-
roundtripStateInternal = null;
89+
out.defaultWriteObject();
7190
}
72-
out.defaultWriteObject();
73-
}
7491

75-
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
76-
in.defaultReadObject();
77-
}
92+
public FormatterStep rehydrate() {
93+
try {
94+
throw new Exception("rehydrate optimizeForEquality=" + optimizeForEquality + " orig=" + original + " cleaned=" + cleaned);
95+
} catch (Exception e) {
96+
e.printStackTrace();
97+
}
98+
return original != null ? original : Objects.requireNonNull(cleaned, "how is clean null if this has been serialized?");
99+
}
100+
101+
@Override
102+
public boolean equals(Object o) {
103+
if (this == o)
104+
return true;
105+
if (o == null || getClass() != o.getClass())
106+
return false;
107+
HackClone<?, ?> that = (HackClone<?, ?>) o;
108+
return optimizeForEquality == that.optimizeForEquality && rehydrate().equals(that.rehydrate());
109+
}
78110

79-
private void readObjectNoData() throws ObjectStreamException {
80-
throw new UnsupportedOperationException();
111+
@Override
112+
public int hashCode() {
113+
return rehydrate().hashCode() ^ Boolean.hashCode(optimizeForEquality);
114+
}
81115
}
82116
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.io.File;
1919
import java.nio.charset.Charset;
20-
import java.util.Collections;
2120
import java.util.List;
2221
import java.util.Locale;
2322
import java.util.Objects;
@@ -150,17 +149,17 @@ public File getOutputDirectory() {
150149
return outputDirectory;
151150
}
152151

153-
private final List<FormatterStep> stepsInternalRoundtrip = new ConfigurationCacheHack.StepList(ConfigurationCacheHack.OptimizeFor.ROUNDTRIP);
154-
private final List<FormatterStep> stepsInternalEquality = new ConfigurationCacheHack.StepList(ConfigurationCacheHack.OptimizeFor.EQUALITY);
152+
private final ConfigurationCacheHack.StepList stepsInternalRoundtrip = new ConfigurationCacheHack.StepList(ConfigurationCacheHack.OptimizeFor.ROUNDTRIP);
153+
private final ConfigurationCacheHack.StepList stepsInternalEquality = new ConfigurationCacheHack.StepList(ConfigurationCacheHack.OptimizeFor.EQUALITY);
155154

156155
@Internal
157-
public List<FormatterStep> getStepsInternalRoundtrip() {
158-
return Collections.unmodifiableList(stepsInternalRoundtrip);
156+
public ConfigurationCacheHack.StepList getStepsInternalRoundtrip() {
157+
return stepsInternalRoundtrip;
159158
}
160159

161160
@Input
162-
public List<FormatterStep> getStepsInternalEquality() {
163-
return Collections.unmodifiableList(stepsInternalEquality);
161+
public ConfigurationCacheHack.StepList getStepsInternalEquality() {
162+
return stepsInternalEquality;
164163
}
165164

166165
public void setSteps(List<FormatterStep> steps) {
@@ -187,7 +186,7 @@ Formatter buildFormatter() {
187186
.lineEndingsPolicy(getLineEndingsPolicy().get())
188187
.encoding(Charset.forName(encoding))
189188
.rootDir(getProjectDir().get().getAsFile().toPath())
190-
.steps(stepsInternalRoundtrip)
189+
.steps(stepsInternalRoundtrip.getSteps())
191190
.exceptionPolicy(exceptionPolicy)
192191
.build();
193192
}

0 commit comments

Comments
 (0)