Skip to content

Migrate FenceStep and the toggleOffOn to support ConfigurationCacheHackList #2378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 6, 2025
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
### Changed
* **BREAKING** Moved `PaddedCell.DirtyState` to its own top-level class with new methods. ([#2148](https://github.com/diffplug/spotless/pull/2148))
* **BREAKING** Removed `isClean`, `applyTo`, and `applyToAndReturnResultIfDirty` from `Formatter` because users should instead use `DirtyState`.
* `FenceStep` now uses `ConfigurationCacheHack`. ([#2378](https://github.com/diffplug/spotless/pull/2378) fixes [#2317](https://github.com/diffplug/spotless/issues/2317))
### Fixed
* `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))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 DiffPlug
* Copyright 2024-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 @@ -15,11 +15,15 @@
*/
package com.diffplug.spotless;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* Gradle requires three things:
* - Gradle defines cache equality based on your serialized representation
Expand Down Expand Up @@ -48,8 +52,28 @@
*/
public class ConfigurationCacheHackList implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private final boolean optimizeForEquality;
private final ArrayList<Object> backingList = new ArrayList<>();
private boolean optimizeForEquality;
private ArrayList<Object> backingList = new ArrayList<>();

private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.writeBoolean(optimizeForEquality);
out.writeInt(backingList.size());
for (Object obj : backingList) {
// if write out the list on its own, we'll get java's non-deterministic object-graph serialization
// by writing each object to raw bytes independently, we avoid this
out.writeObject(LazyForwardingEquality.toBytes((Serializable) obj));
}
}

@SuppressFBWarnings("MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT")
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
optimizeForEquality = in.readBoolean();
backingList = new ArrayList<>();
int size = in.readInt();
for (int i = 0; i < size; i++) {
backingList.add(LazyForwardingEquality.fromBytes((byte[]) in.readObject()));
}
}

public static ConfigurationCacheHackList forEquality() {
return new ConfigurationCacheHackList(true);
Expand Down
6 changes: 3 additions & 3 deletions lib/src/main/java/com/diffplug/spotless/FormatterStep.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 Down Expand Up @@ -106,7 +106,7 @@ static <RoundtripState extends Serializable, EqualityState extends Serializable>
String name,
ThrowingEx.Supplier<RoundtripState> roundtripInit,
SerializedFunction<RoundtripState, EqualityState> equalityFunc,
SerializedFunction<EqualityState, FormatterFunc> formatterFunc) {
SerializedFunction<EqualityState, ? extends FormatterFunc> formatterFunc) {
return new FormatterStepSerializationRoundtrip<>(name, roundtripInit, equalityFunc, formatterFunc);
}

Expand All @@ -128,7 +128,7 @@ static <RoundtripState extends Serializable, EqualityState extends Serializable>
String name,
RoundtripState roundTrip,
SerializedFunction<RoundtripState, EqualityState> equalityFunc,
SerializedFunction<EqualityState, FormatterFunc> formatterFunc) {
SerializedFunction<EqualityState, ? extends FormatterFunc> formatterFunc) {
return createLazy(name, () -> roundTrip, equalityFunc, formatterFunc);
}

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

FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier<RoundtripState> initializer, SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor, SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter) {
FormatterStepSerializationRoundtrip(String name, ThrowingEx.Supplier<RoundtripState> initializer, SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor, SerializedFunction<EqualityState, ? extends FormatterFunc> equalityStateToFormatter) {
this.name = name;
this.initializer = initializer;
this.equalityStateExtractor = equalityStateExtractor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2023 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 @@ -15,6 +15,7 @@
*/
package com.diffplug.spotless;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
Expand Down Expand Up @@ -112,6 +113,15 @@ static byte[] toBytes(Serializable obj) {
return byteOutput.toByteArray();
}

static Object fromBytes(byte[] bytes) {
ByteArrayInputStream byteOutput = new ByteArrayInputStream(bytes);
try (ObjectInputStream objectOutput = new ObjectInputStream(byteOutput)) {
return objectOutput.readObject();
} catch (IOException | ClassNotFoundException e) {
throw ThrowingEx.asRuntime(e);
}
}

/** Ensures that the lazy state has been evaluated. */
public static void unlazy(Object in) {
if (in instanceof LazyForwardingEquality) {
Expand Down
157 changes: 69 additions & 88 deletions lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2024 DiffPlug
* Copyright 2020-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 @@ -24,9 +24,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import com.diffplug.spotless.ConfigurationCacheHackList;
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.Lint;
Expand Down Expand Up @@ -80,94 +80,83 @@ private void assertRegexSet() {

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

/**
* Returns a step which will apply the given steps only within the blocks selected by the regex / openClose pair.
* Linting within the substeps is not supported.
*/
public FormatterStep applyWithin(List<FormatterStep> steps) {
return createStep(Kind.APPLY, steps);
}

private FormatterStep createStep(Kind kind, List<FormatterStep> steps) {
assertRegexSet();
return new ApplyWithin(name, regex, steps);
return FormatterStep.createLazy(name, () -> new RoundtripAndEqualityState(kind, regex, steps, false),
RoundtripAndEqualityState::toEqualityState,
RoundtripAndEqualityState::toFormatterFunc);
}

static class ApplyWithin extends BaseStep {
private static final long serialVersionUID = 17061466531957339L;
private enum Kind {
APPLY, PRESERVE
}

ApplyWithin(String name, Pattern regex, List<FormatterStep> steps) {
super(name, regex, steps);
}
private static class RoundtripAndEqualityState implements Serializable {
private static final long serialVersionUID = 272603249547598947L;
final String regexPattern;
final int regexFlags;
final Kind kind;
final ConfigurationCacheHackList steps;

@Override
protected String applySubclass(Formatter formatter, String unix, File file) {
List<String> groups = groupsZeroed();
Matcher matcher = regex.matcher(unix);
while (matcher.find()) {
// apply the formatter to each group
groups.add(formatter.compute(matcher.group(1), file));
}
// and then assemble the result right away
return assembleGroups(unix);
/** Roundtrip state. */
private RoundtripAndEqualityState(Kind kind, Pattern regex, List<FormatterStep> steps, boolean optimizeForEquality) {
this.kind = kind;
this.regexPattern = regex.pattern();
this.regexFlags = regex.flags();
this.steps = optimizeForEquality ? ConfigurationCacheHackList.forEquality() : ConfigurationCacheHackList.forRoundtrip();
this.steps.addAll(steps);
}
}

static class PreserveWithin extends BaseStep {
private static final long serialVersionUID = -8676786492305178343L;
private Pattern regex() {
return Pattern.compile(regexPattern, regexFlags);
}

PreserveWithin(String name, Pattern regex, List<FormatterStep> steps) {
super(name, regex, steps);
private List<FormatterStep> steps() {
return steps.getSteps();
}

private void storeGroups(String unix) {
List<String> groups = groupsZeroed();
Matcher matcher = regex.matcher(unix);
while (matcher.find()) {
// store whatever is within the open/close tags
groups.add(matcher.group(1));
}
public RoundtripAndEqualityState toEqualityState() {
return new RoundtripAndEqualityState(kind, regex(), steps(), true);
}

@Override
protected String applySubclass(Formatter formatter, String unix, File file) {
storeGroups(unix);
String formatted = formatter.compute(unix, file);
return assembleGroups(formatted);
public BaseFormatter toFormatterFunc() {
return new BaseFormatter(kind, this);
}
}

@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
private static abstract class BaseStep implements Serializable, FormatterStep {
final String name;
private static final long serialVersionUID = -2301848328356559915L;
private static class BaseFormatter implements FormatterFunc.NeedsFile, FormatterFunc.Closeable {
final Kind kind;
final Pattern regex;
final List<FormatterStep> steps;

transient ArrayList<String> groups = new ArrayList<>();
transient StringBuilder builderInternal;
final ArrayList<String> groups = new ArrayList<>();
final StringBuilder builderInternal = new StringBuilder();

public BaseStep(String name, Pattern regex, List<FormatterStep> steps) {
this.name = name;
this.regex = regex;
this.steps = steps;
public BaseFormatter(Kind kind, RoundtripAndEqualityState state) {
this.kind = kind;
this.regex = state.regex();
this.steps = state.steps();
}

protected ArrayList<String> groupsZeroed() {
if (groups == null) {
groups = new ArrayList<>();
} else {
groups.clear();
}
groups.clear();
return groups;
}

private StringBuilder builderZeroed() {
if (builderInternal == null) {
builderInternal = new StringBuilder();
} else {
builderInternal.setLength(0);
}
builderInternal.setLength(0);
return builderInternal;
}

Expand Down Expand Up @@ -215,41 +204,33 @@ protected String assembleGroups(String unix) {
}
}

@Override
public String getName() {
return name;
}
private Formatter formatter;

private transient Formatter formatter;

private String apply(String rawUnix, File file) throws Exception {
@Override
public String applyWithFile(String unix, File file) throws Exception {
if (formatter == null) {
formatter = buildFormatter();
}
return applySubclass(formatter, rawUnix, file);
}

@Nullable
@Override
public String format(String rawUnix, File file) throws Exception {
return apply(rawUnix, file);
}

protected abstract String applySubclass(Formatter formatter, String unix, File file) throws Exception;

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
BaseStep step = (BaseStep) o;
return name.equals(step.name) && regex.pattern().equals(step.regex.pattern()) && regex.flags() == step.regex.flags() && steps.equals(step.steps);
}

@Override
public int hashCode() {
return Objects.hash(name, regex.pattern(), regex.flags(), steps);
List<String> groups = groupsZeroed();
Matcher matcher = regex.matcher(unix);
switch (kind) {
case APPLY:
while (matcher.find()) {
// apply the formatter to each group
groups.add(formatter.compute(matcher.group(1), file));
}
// and then assemble the result right away
return assembleGroups(unix);
case PRESERVE:
while (matcher.find()) {
// store whatever is within the open/close tags
groups.add(matcher.group(1));
}
String formatted = formatter.compute(unix, file);
return assembleGroups(formatted);
default:
throw new Error();
}
}

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

GoogleJavaFormatStep step = new GoogleJavaFormatStep(JarState.promise(() -> JarState.from(groupArtifact + ":" + version, provisioner)), version, style, reflowLongStrings, reorderImports, formatJavadoc);
if (removeImports) {
return FormatterStep.create(NAME,
return FormatterStep.create(name,
step,
GoogleJavaFormatStep::equalityState,
State::createRemoveUnusedImportsOnly);
} else {
return FormatterStep.create(NAME,
return FormatterStep.create(name,
step,
GoogleJavaFormatStep::equalityState,
State::createFormat);
Expand Down
1 change: 1 addition & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* Bump default `ktlint` version to latest `1.4.0` -> `1.5.0`. ([#2354](https://github.com/diffplug/spotless/pull/2354))
* Bump minimum `eclipse-cdt` version to `11.0` (removed support for `10.7`). ([#2373](https://github.com/diffplug/spotless/pull/2373))
### Fixed
* `toggleOffOn` now works with the configuration cache. ([#2378](https://github.com/diffplug/spotless/pull/2378) fixes [#2317](https://github.com/diffplug/spotless/issues/2317))
* You can now use `removeUnusedImports` and `googleJavaFormat` at the same time again. (fixes [#2159](https://github.com/diffplug/spotless/issues/2159))
* 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))
* `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))
Expand Down
Loading
Loading