Skip to content

Commit

Permalink
Moved PaddedCell.DirtyState to its own top-level class with new met…
Browse files Browse the repository at this point in the history
…hods.
  • Loading branch information
nedtwigg committed Jan 14, 2022
1 parent b3462c2 commit fe898e8
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 127 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
## [TBD lint release]
* **BREAKING** Removed `isClean`, `applyTo`, and `applyToAndReturnResultIfDirty` from `Formatter` because users should instead use `PaddedCell.check()`.
* **BREAKING** Removed `FormatterStep.Strict` because it was unnecessary and unused implementation detail.
* **BREAKING** Moved `PaddedCell.DirtyState` to its own top-level class with new methods.

## [Unreleased]
### Changed
Expand Down
141 changes: 141 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/DirtyState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2022 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.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays;

import javax.annotation.Nullable;

/**
* The clean/dirty state of a single file. Intended use:
* - {@link #isClean()} means that the file is is clean, and there's nothing else to say
* - {@link #didNotConverge()} means that we were unable to determine a clean state
* - once you've tested the above conditions and you know that it's a dirty file with a converged state,
* then you can call {@link #writeCanonicalTo(OutputStream)} to get the canonical form of the given file.
*/
public class DirtyState {
@Nullable
private final byte[] canonicalBytes;

DirtyState(@Nullable byte[] canonicalBytes) {
this.canonicalBytes = canonicalBytes;
}

public boolean isClean() {
return this == isClean;
}

public boolean didNotConverge() {
return this == didNotConverge;
}

private byte[] canonicalBytes() {
if (canonicalBytes == null) {
throw new IllegalStateException("First make sure that {@code !isClean()} and {@code !didNotConverge()}");
}
return canonicalBytes;
}

public void writeCanonicalTo(File file) throws IOException {
Files.write(file.toPath(), canonicalBytes());
}

public void writeCanonicalTo(OutputStream out) throws IOException {
out.write(canonicalBytes());
}

/** Returns the DirtyState which corresponds to {@code isClean()}. */
public static DirtyState clean() {
return isClean;
}

static final DirtyState didNotConverge = new DirtyState(null);
static final DirtyState isClean = new DirtyState(null);

public static Calculation of(Formatter formatter, File file) throws IOException {
return of(formatter, file, Files.readAllBytes(file.toPath()));
}

public static Calculation of(Formatter formatter, File file, byte[] rawBytes) {
return new Calculation(formatter, file, rawBytes);
}

public static class Calculation {
private final Formatter formatter;
private final File file;
private final byte[] rawBytes;
private final String raw;

private Calculation(Formatter formatter, File file, byte[] rawBytes) {
this.formatter = formatter;
this.file = file;
this.rawBytes = rawBytes;
this.raw = new String(rawBytes, formatter.getEncoding());
// check that all characters were encodable
String encodingError = EncodingErrorMsg.msg(raw, rawBytes, formatter.getEncoding());
if (encodingError != null) {
throw new IllegalArgumentException(encodingError);
}
}

/**
* Calculates whether the given file is dirty according to a PaddedCell invocation of the given formatter.
* DirtyState includes the clean state of the file, as well as a warning if we were not able to apply the formatter
* due to diverging idempotence.
*/
public DirtyState calculateDirtyState() {
String rawUnix = LineEnding.toUnix(raw);

// enforce the format
String formattedUnix = formatter.compute(rawUnix, file);
// convert the line endings if necessary
String formatted = formatter.computeLineEndings(formattedUnix, file);

// if F(input) == input, then the formatter is well-behaving and the input is clean
byte[] formattedBytes = formatted.getBytes(formatter.getEncoding());
if (Arrays.equals(rawBytes, formattedBytes)) {
return isClean;
}

// F(input) != input, so we'll do a padded check
String doubleFormattedUnix = formatter.compute(formattedUnix, file);
if (doubleFormattedUnix.equals(formattedUnix)) {
// most dirty files are idempotent-dirty, so this is a quick-short circuit for that common case
return new DirtyState(formattedBytes);
}

PaddedCell cell = PaddedCell.check(formatter, file, rawUnix);
if (!cell.isResolvable()) {
return didNotConverge;
}

// get the canonical bytes
String canonicalUnix = cell.canonical();
String canonical = formatter.computeLineEndings(canonicalUnix, file);
byte[] canonicalBytes = canonical.getBytes(formatter.getEncoding());
if (!Arrays.equals(rawBytes, canonicalBytes)) {
// and write them to disk if needed
return new DirtyState(canonicalBytes);
} else {
return isClean;
}
}
}
}
111 changes: 1 addition & 110 deletions lib/src/main/java/com/diffplug/spotless/PaddedCell.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 DiffPlug
* Copyright 2016-2022 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 @@ -18,19 +18,14 @@
import static com.diffplug.spotless.LibPreconditions.requireElementsNonNull;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import javax.annotation.Nullable;

/**
* Models the result of applying a {@link Formatter} on a given {@link File}
* while characterizing various failure modes (slow convergence, cycles, and divergence).
Expand Down Expand Up @@ -176,108 +171,4 @@ public String userMessage() {
}
// @formatter:on
}

/**
* Calculates whether the given file is dirty according to a PaddedCell invocation of the given formatter.
* DirtyState includes the clean state of the file, as well as a warning if we were not able to apply the formatter
* due to diverging idempotence.
*/
public static DirtyState calculateDirtyState(Formatter formatter, File file) throws IOException {
Objects.requireNonNull(formatter, "formatter");
Objects.requireNonNull(file, "file");

byte[] rawBytes = Files.readAllBytes(file.toPath());
return calculateDirtyState(formatter, file, rawBytes);
}

public static DirtyState calculateDirtyState(Formatter formatter, File file, byte[] rawBytes) throws IOException {
String raw = new String(rawBytes, formatter.getEncoding());
// check that all characters were encodable
String encodingError = EncodingErrorMsg.msg(raw, rawBytes, formatter.getEncoding());
if (encodingError != null) {
throw new IllegalArgumentException(encodingError);
}
String rawUnix = LineEnding.toUnix(raw);

// enforce the format
String formattedUnix = formatter.compute(rawUnix, file);
// convert the line endings if necessary
String formatted = formatter.computeLineEndings(formattedUnix, file);

// if F(input) == input, then the formatter is well-behaving and the input is clean
byte[] formattedBytes = formatted.getBytes(formatter.getEncoding());
if (Arrays.equals(rawBytes, formattedBytes)) {
return isClean;
}

// F(input) != input, so we'll do a padded check
String doubleFormattedUnix = formatter.compute(formattedUnix, file);
if (doubleFormattedUnix.equals(formattedUnix)) {
// most dirty files are idempotent-dirty, so this is a quick-short circuit for that common case
return new DirtyState(formattedBytes);
}

PaddedCell cell = PaddedCell.check(formatter, file, rawUnix);
if (!cell.isResolvable()) {
return didNotConverge;
}

// get the canonical bytes
String canonicalUnix = cell.canonical();
String canonical = formatter.computeLineEndings(canonicalUnix, file);
byte[] canonicalBytes = canonical.getBytes(formatter.getEncoding());
if (!Arrays.equals(rawBytes, canonicalBytes)) {
// and write them to disk if needed
return new DirtyState(canonicalBytes);
} else {
return isClean;
}
}

/**
* The clean/dirty state of a single file. Intended use:
* - {@link #isClean()} means that the file is is clean, and there's nothing else to say
* - {@link #didNotConverge()} means that we were unable to determine a clean state
* - once you've tested the above conditions and you know that it's a dirty file with a converged state,
* then you can call {@link #writeCanonicalTo(OutputStream)} to get the canonical form of the given file.
*/
public static class DirtyState {
@Nullable
private final byte[] canonicalBytes;

private DirtyState(@Nullable byte[] canonicalBytes) {
this.canonicalBytes = canonicalBytes;
}

public boolean isClean() {
return this == isClean;
}

public boolean didNotConverge() {
return this == didNotConverge;
}

private byte[] canonicalBytes() {
if (canonicalBytes == null) {
throw new IllegalStateException("First make sure that {@code !isClean()} and {@code !didNotConverge()}");
}
return canonicalBytes;
}

public void writeCanonicalTo(File file) throws IOException {
Files.write(file.toPath(), canonicalBytes());
}

public void writeCanonicalTo(OutputStream out) throws IOException {
out.write(canonicalBytes());
}
}

/** Returns the DirtyState which corresponds to {@code isClean()}. */
public static DirtyState isClean() {
return isClean;
}

private static final DirtyState didNotConverge = new DirtyState(null);
private static final DirtyState isClean = new DirtyState(null);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 DiffPlug
* Copyright 2016-2022 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 @@ -17,12 +17,11 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import com.diffplug.common.base.Errors;
import com.diffplug.common.io.ByteStreams;
import com.diffplug.spotless.DirtyState;
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.PaddedCell;

class IdeHook {
final static String PROPERTY = "spotlessIdeHook";
Expand All @@ -49,13 +48,14 @@ static void performHook(SpotlessTaskImpl spotlessTask) {
return;
}
}
DirtyState.Calculation init;
byte[] bytes;
if (spotlessTask.getProject().hasProperty(USE_STD_IN)) {
bytes = ByteStreams.toByteArray(System.in);
init = DirtyState.of(formatter, file, ByteStreams.toByteArray(System.in));
} else {
bytes = Files.readAllBytes(file.toPath());
init = DirtyState.of(formatter, file);
}
PaddedCell.DirtyState dirty = PaddedCell.calculateDirtyState(formatter, file, bytes);
DirtyState dirty = init.calculateDirtyState();
if (dirty.isClean()) {
dumpIsClean();
} else if (dirty.didNotConverge()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 DiffPlug
* Copyright 2016-2022 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 @@ -36,8 +36,8 @@
import org.gradle.work.InputChanges;

import com.diffplug.common.base.StringPrinter;
import com.diffplug.spotless.DirtyState;
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.PaddedCell;
import com.diffplug.spotless.extra.GitRatchet;

@CacheableTask
Expand Down Expand Up @@ -85,11 +85,11 @@ public void performAction(InputChanges inputs) throws Exception {
private void processInputFile(@Nullable GitRatchet ratchet, Formatter formatter, File input) throws IOException {
File output = getOutputFile(input);
getLogger().debug("Applying format to " + input + " and writing to " + output);
PaddedCell.DirtyState dirtyState;
DirtyState dirtyState;
if (ratchet != null && ratchet.isClean(getProjectDir().get().getAsFile(), getRootTreeSha(), input)) {
dirtyState = PaddedCell.isClean();
dirtyState = DirtyState.clean();
} else {
dirtyState = PaddedCell.calculateDirtyState(formatter, input);
dirtyState = DirtyState.of(formatter, input).calculateDirtyState();
}
if (dirtyState.isClean()) {
// Remove previous output if it exists
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2021 DiffPlug
* Copyright 2016-2022 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 @@ -21,8 +21,8 @@
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;

import com.diffplug.spotless.DirtyState;
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.PaddedCell;
import com.diffplug.spotless.maven.incremental.UpToDateChecker;

/**
Expand All @@ -42,7 +42,7 @@ protected void process(Iterable<File> files, Formatter formatter, UpToDateChecke
}

try {
PaddedCell.DirtyState dirtyState = PaddedCell.calculateDirtyState(formatter, file);
DirtyState dirtyState = DirtyState.of(formatter, file).calculateDirtyState();
if (!dirtyState.isClean() && !dirtyState.didNotConverge()) {
dirtyState.writeCanonicalTo(file);
}
Expand Down

0 comments on commit fe898e8

Please sign in to comment.