Skip to content

Commit

Permalink
proof-of-concept: ideally we'd leverage palantir/palantir-java-format…
Browse files Browse the repository at this point in the history
  • Loading branch information
carterkozak committed Oct 28, 2021
1 parent 45b5308 commit e23da71
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.goethe;

import com.google.common.io.ByteStreams;
import com.palantir.javaformat.java.Main;
import com.squareup.javapoet.JavaFile;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

final class BootstrappingFormatterFacade implements FormatterFacade {

@Override
public String formatSource(JavaFile file) throws GoetheException {
try {
Process process = new ProcessBuilder(
new File(System.getProperty("java.home"), "bin/java").getAbsolutePath(),
// Exports
"--add-exports",
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
// Classpath
"-cp",
System.getProperty("java.class.path"),
// Main class
Main.class.getName(),
// Args
"--palantir",
"--skip-reflowing-long-strings",
"-")
.start();
StringBuilder rawSource = new StringBuilder();
file.writeTo(rawSource);
try (OutputStream outputStream = process.getOutputStream()) {
outputStream.write(rawSource.toString().getBytes(StandardCharsets.UTF_8));
}
byte[] data;
try (InputStream inputStream = process.getInputStream()) {
data = ByteStreams.toByteArray(inputStream);
}
int exitStatus = process.waitFor();
if (exitStatus != 0) {
throw new GoetheException("Bootstrapped formatter exited non-zero: " + exitStatus + " with output:\n"
+ getErrorOutput(process));
}
return new String(data, StandardCharsets.UTF_8);
} catch (IOException | InterruptedException e) {
throw new GoetheException("Failed to bootstrap jdk", e);
}
}

private static String getErrorOutput(Process process) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (InputStream inputStream = process.getErrorStream()) {
ByteStreams.copy(inputStream, baos);
} catch (IOException | RuntimeException ignored) {
// continue
}
return baos.toString(StandardCharsets.UTF_8);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.goethe;

import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.palantir.javaformat.java.Formatter;
import com.palantir.javaformat.java.FormatterDiagnostic;
import com.palantir.javaformat.java.FormatterException;
import com.palantir.javaformat.java.JavaFormatterOptions;
import com.squareup.javapoet.JavaFile;
import java.io.IOException;
import java.util.List;

final class DirectFormatterFacade implements FormatterFacade {

private final Formatter formatter = Formatter.createFormatter(JavaFormatterOptions.builder()
.style(JavaFormatterOptions.Style.PALANTIR)
.build());

@Override
public String formatSource(JavaFile file) throws GoetheException {
StringBuilder rawSource = new StringBuilder();
try {
file.writeTo(rawSource);
return formatter.formatSource(rawSource.toString());
} catch (FormatterException e) {
throw new GoetheException(generateMessage(file, rawSource.toString(), e.diagnostics()), e);
} catch (IOException e) {
throw new GoetheException("Formatting failed", e);
}
}

/**
* Attempt to provide as much actionable information as possible to understand why formatting is failing. This
* is common when generated code is incorrect and cannot compile, so we mustn't make it difficult to understand
* the problem.
*/
private static String generateMessage(
JavaFile file, String unformattedSource, List<FormatterDiagnostic> formatterDiagnostics) {
try {
List<String> lines = Splitter.on('\n').splitToList(unformattedSource);
StringBuilder failureText = new StringBuilder();
failureText
.append("Failed to format '")
.append(file.packageName)
.append('.')
.append(file.typeSpec.name)
.append("'\n");
for (FormatterDiagnostic formatterDiagnostic : formatterDiagnostics) {
failureText
.append(formatterDiagnostic.message())
.append("\n")
// Diagnostic values are one-indexed, while our list is zero-indexed.
.append(lines.get(formatterDiagnostic.line() - 1))
.append('\n')
// Offset by two to convert from one-indexed to zero indexed values, and account for the caret.
.append(Strings.repeat(" ", Math.max(0, formatterDiagnostic.column() - 2)))
.append("^\n\n");
}
return CharMatcher.is('\n').trimFrom(failureText.toString());
} catch (RuntimeException e) {
return "Failed to format:\n" + unformattedSource;
}
}
}
24 changes: 24 additions & 0 deletions goethe/src/main/java/com/palantir/goethe/FormatterFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.goethe;

import com.squareup.javapoet.JavaFile;

interface FormatterFacade {

String formatSource(JavaFile file) throws GoetheException;
}
55 changes: 3 additions & 52 deletions goethe/src/main/java/com/palantir/goethe/Goethe.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,14 @@

package com.palantir.goethe;

import com.google.common.base.CharMatcher;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.palantir.javaformat.java.Formatter;
import com.palantir.javaformat.java.FormatterDiagnostic;
import com.palantir.javaformat.java.FormatterException;
import com.palantir.javaformat.java.JavaFormatterOptions;
import com.squareup.javapoet.JavaFile;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.tools.JavaFileObject;
Expand All @@ -46,9 +39,8 @@
*/
public final class Goethe {

private static final Formatter JAVA_FORMATTER = Formatter.createFormatter(JavaFormatterOptions.builder()
.style(JavaFormatterOptions.Style.PALANTIR)
.build());
private static final FormatterFacade JAVA_FORMATTER =
new BootstrappingFormatterFacade(); // new DirectFormatterFacade();

/**
* Format a {@link JavaFile javapoet java file} into a {@link String}.
Expand All @@ -57,15 +49,7 @@ public final class Goethe {
* @return Formatted source code
*/
public static String formatAsString(JavaFile file) {
StringBuilder rawSource = new StringBuilder();
try {
file.writeTo(rawSource);
return JAVA_FORMATTER.formatSource(rawSource.toString());
} catch (FormatterException e) {
throw new GoetheException(generateMessage(file, rawSource.toString(), e.diagnostics()), e);
} catch (IOException e) {
throw new GoetheException("Formatting failed", e);
}
return JAVA_FORMATTER.formatSource(file);
}

/**
Expand Down Expand Up @@ -115,39 +99,6 @@ public static Path formatAndEmit(JavaFile file, Path baseDir) {
}
}

/**
* Attempt to provide as much actionable information as possible to understand why formatting is failing. This
* is common when generated code is incorrect and cannot compile, so we mustn't make it difficult to understand
* the problem.
*/
private static String generateMessage(
JavaFile file, String unformattedSource, List<FormatterDiagnostic> formatterDiagnostics) {
try {
List<String> lines = Splitter.on('\n').splitToList(unformattedSource);
StringBuilder failureText = new StringBuilder();
failureText
.append("Failed to format '")
.append(file.packageName)
.append('.')
.append(file.typeSpec.name)
.append("'\n");
for (FormatterDiagnostic formatterDiagnostic : formatterDiagnostics) {
failureText
.append(formatterDiagnostic.message())
.append("\n")
// Diagnostic values are one-indexed, while our list is zero-indexed.
.append(lines.get(formatterDiagnostic.line() - 1))
.append('\n')
// Offset by two to convert from one-indexed to zero indexed values, and account for the caret.
.append(Strings.repeat(" ", Math.max(0, formatterDiagnostic.column() - 2)))
.append("^\n\n");
}
return CharMatcher.is('\n').trimFrom(failureText.toString());
} catch (RuntimeException e) {
return "Failed to format:\n" + unformattedSource;
}
}

private static String className(JavaFile file) {
return file.packageName.isEmpty() ? file.typeSpec.name : file.packageName + "." + file.typeSpec.name;
}
Expand Down
4 changes: 4 additions & 0 deletions goethe/src/main/java/com/palantir/goethe/GoetheException.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

/** Marker exception describing failures emitted from the Goethe library. */
public final class GoetheException extends IllegalStateException {
GoetheException(String message) {
super(message);
}

GoetheException(String message, Throwable cause) {
super(message, cause);
}
Expand Down

0 comments on commit e23da71

Please sign in to comment.