142 lines
6.5 KiB
Java
142 lines
6.5 KiB
Java
import com.palantir.javaformat.java.Formatter;
|
|
import com.palantir.javaformat.java.JavaFormatterOptions;
|
|
import java.nio.file.*;
|
|
import java.security.MessageDigest;
|
|
import java.util.*;
|
|
import org.openrewrite.*;
|
|
import org.openrewrite.internal.InMemoryLargeSourceSet;
|
|
import org.openrewrite.text.*;
|
|
|
|
/** Source-launcher bootstrap: no project-specific artifact is resolved or installed. */
|
|
public class Format {
|
|
/**
|
|
* Checks or atomically formats configured handwritten Java sources with the pinned formatter.
|
|
*
|
|
* @param args consumer repository root followed by either {@code check} or {@code apply}
|
|
* @throws Exception if configuration, parsing or formatting fails, or check mode finds differences
|
|
*/
|
|
public static void main(final String... args) throws Exception {
|
|
Path root = Path.of(args[0]).toAbsolutePath().normalize();
|
|
boolean check = switch (args[1]) {
|
|
case "check" -> true;
|
|
case "apply" -> false;
|
|
default -> throw new IllegalArgumentException("format.mode must be apply or check");
|
|
};
|
|
Path cacheFile = root.resolve("target/palantir-cache.properties");
|
|
var cache = new Properties();
|
|
if (Files.exists(cacheFile)) {
|
|
try (var input = Files.newInputStream(cacheFile)) {
|
|
cache.load(input);
|
|
}
|
|
}
|
|
String configuration = digest(Files.readString(root.resolve("pom.xml"))
|
|
+ Files.readString(root.resolve("generated/tooling/format/Format.java"))
|
|
+ System.getProperty("java.version")
|
|
+ System.getProperty("java.class.path"));
|
|
if (!configuration.equals(cache.getProperty("configuration"))) cache.clear();
|
|
cache.setProperty("configuration", configuration);
|
|
List<SourceFile> sources = new ArrayList<>();
|
|
var settings = new Properties();
|
|
try (var input = Files.newInputStream(root.resolve("engineering.properties"))) {
|
|
settings.load(input);
|
|
}
|
|
for (String directory :
|
|
settings.getProperty("FORMAT_DIRECTORIES", "backend/src").split(",")) {
|
|
if (!Files.isDirectory(root.resolve(directory))) continue;
|
|
try (var paths = Files.walk(root.resolve(directory))) {
|
|
for (Path path : paths.filter(p -> p.toString().endsWith(".java"))
|
|
.sorted()
|
|
.toList()) {
|
|
try {
|
|
String content = Files.readString(path);
|
|
String key = root.relativize(path).toString();
|
|
if (!digest(content).equals(cache.getProperty(key))) {
|
|
sources.add(PlainText.builder()
|
|
.sourcePath(root.relativize(path))
|
|
.text(content)
|
|
.build());
|
|
}
|
|
} catch (Exception error) {
|
|
throw new IllegalStateException(path + ": " + error.getMessage(), error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var context = new InMemoryExecutionContext(error -> {
|
|
throw new IllegalStateException("Formatting failed: " + error.getMessage(), error);
|
|
});
|
|
var results = new PalantirRecipe()
|
|
.run(new InMemoryLargeSourceSet(sources), context)
|
|
.getChangeset()
|
|
.getAllResults();
|
|
for (Result result : results) {
|
|
IO.println((check ? "Needs formatting: " : "Formatted: ")
|
|
+ result.getAfter().getSourcePath());
|
|
}
|
|
if (check && !results.isEmpty()) {
|
|
throw new IllegalStateException("Run mvn -N exec:exec@format to apply Palantir formatting.");
|
|
}
|
|
// Complete parsing/formatting before writing any maintained file. Each replacement is atomic.
|
|
if (!check) {
|
|
for (Result result : results) {
|
|
Path destination = root.resolve(result.getAfter().getSourcePath());
|
|
Path temporary = Files.createTempFile(destination.getParent(), ".palantir-", ".tmp");
|
|
try {
|
|
Files.writeString(temporary, result.getAfter().printAll());
|
|
Files.setPosixFilePermissions(temporary, Files.getPosixFilePermissions(destination));
|
|
Files.move(
|
|
temporary,
|
|
destination,
|
|
StandardCopyOption.ATOMIC_MOVE,
|
|
StandardCopyOption.REPLACE_EXISTING);
|
|
} finally {
|
|
Files.deleteIfExists(temporary);
|
|
}
|
|
}
|
|
}
|
|
for (SourceFile source : sources) {
|
|
cache.setProperty(
|
|
source.getSourcePath().toString(), digest(Files.readString(root.resolve(source.getSourcePath()))));
|
|
}
|
|
Files.createDirectories(cacheFile.getParent());
|
|
try (var output = Files.newOutputStream(cacheFile)) {
|
|
cache.store(output, "Successfully formatted content only");
|
|
}
|
|
IO.println("Palantir " + args[1] + ": " + sources.size() + " Java files, " + results.size() + " differences");
|
|
}
|
|
|
|
static String digest(String value) throws Exception {
|
|
return HexFormat.of()
|
|
.formatHex(MessageDigest.getInstance("SHA-256")
|
|
.digest(value.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
|
|
}
|
|
|
|
public static final class PalantirRecipe extends Recipe {
|
|
public String getDisplayName() {
|
|
return "Palantir Java Format";
|
|
}
|
|
|
|
public String getDescription() {
|
|
return "Apply the pinned Palantir engine and its native import handling.";
|
|
}
|
|
|
|
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
|
return new PlainTextVisitor<ExecutionContext>() {
|
|
final Formatter formatter = Formatter.createFormatter(JavaFormatterOptions.builder()
|
|
.style(JavaFormatterOptions.Style.PALANTIR)
|
|
.formatJavadoc(false)
|
|
.build());
|
|
|
|
@Override
|
|
public PlainText visitText(PlainText text, ExecutionContext context) {
|
|
try {
|
|
return text.withText(formatter.formatSourceAndFixImports(text.getText()));
|
|
} catch (Exception | LinkageError error) {
|
|
throw new IllegalStateException(text.getSourcePath() + ": " + error, error);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|