Skip to content

Commit

Permalink
Migrate FenceStep and the toggleOffOn to support `ConfigurationCa…
Browse files Browse the repository at this point in the history
…cheHackList` (#2378 fixes #2317)
  • Loading branch information
nedtwigg authored Jan 6, 2025
2 parents 55a7db4 + 99a32ff commit a52fa2f
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 112 deletions.
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

0 comments on commit a52fa2f

Please sign in to comment.