|
| 1 | +/* |
| 2 | + * Copyright 2016-2022 DiffPlug |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package com.diffplug.spotless.java; |
| 17 | + |
| 18 | +import java.io.Serializable; |
| 19 | +import java.lang.reflect.Method; |
| 20 | +import java.util.Objects; |
| 21 | + |
| 22 | +import com.diffplug.spotless.*; |
| 23 | +import com.diffplug.spotless.ThrowingEx.Function; |
| 24 | + |
| 25 | +/** Wraps up <a href="https://github.com/palantir/palantir-java-format">palantir-java-format</a> fork of |
| 26 | + * <a href="https://github.com/google/google-java-format">google-java-format</a> as a FormatterStep. */ |
| 27 | +public class PalantirJavaFormatStep { |
| 28 | + // prevent direct instantiation |
| 29 | + private PalantirJavaFormatStep() {} |
| 30 | + |
| 31 | + private static final String DEFAULT_STYLE = "PALANTIR"; |
| 32 | + static final String NAME = "palantir-java-format"; |
| 33 | + static final String MAVEN_COORDINATE = "com.palantir.javaformat:palantir-java-format"; |
| 34 | + static final String FORMATTER_CLASS = "com.palantir.javaformat.java.Formatter"; |
| 35 | + static final String FORMATTER_METHOD = "formatSource"; |
| 36 | + |
| 37 | + private static final String OPTIONS_CLASS = "com.palantir.javaformat.java.JavaFormatterOptions"; |
| 38 | + private static final String OPTIONS_BUILDER_METHOD = "builder"; |
| 39 | + private static final String OPTIONS_BUILDER_CLASS = "com.palantir.javaformat.java.JavaFormatterOptions$Builder"; |
| 40 | + private static final String OPTIONS_BUILDER_STYLE_METHOD = "style"; |
| 41 | + private static final String OPTIONS_BUILDER_BUILD_METHOD = "build"; |
| 42 | + private static final String OPTIONS_Style = "com.palantir.javaformat.java.JavaFormatterOptions$Style"; |
| 43 | + private static final String OPTIONS_MAX_LINE_LENGTH_METHOD = "maxLineLength"; |
| 44 | + |
| 45 | + private static final String REMOVE_UNUSED_CLASS = "com.palantir.javaformat.java.RemoveUnusedImports"; |
| 46 | + private static final String REMOVE_UNUSED_METHOD = "removeUnusedImports"; |
| 47 | + |
| 48 | + private static final String IMPORT_ORDERER_CLASS = "com.palantir.javaformat.java.ImportOrderer"; |
| 49 | + private static final String IMPORT_ORDERER_METHOD = "reorderImports"; |
| 50 | + |
| 51 | + /** Creates a step which formats everything - code, import order, and unused imports. */ |
| 52 | + public static FormatterStep create(Provisioner provisioner) { |
| 53 | + return create(defaultVersion(), provisioner); |
| 54 | + } |
| 55 | + |
| 56 | + /** Creates a step which formats everything - code, import order, and unused imports. */ |
| 57 | + public static FormatterStep create(String version, Provisioner provisioner) { |
| 58 | + return create(version, DEFAULT_STYLE, provisioner); |
| 59 | + } |
| 60 | + |
| 61 | + /** Creates a step which formats everything - code, import order, and unused imports. */ |
| 62 | + public static FormatterStep create(String version, String style, Provisioner provisioner) { |
| 63 | + return create(MAVEN_COORDINATE, version, style, provisioner); |
| 64 | + } |
| 65 | + |
| 66 | + /** Creates a step which formats everything - groupArtifact, code, import order, and unused imports. */ |
| 67 | + public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner) { |
| 68 | + Objects.requireNonNull(groupArtifact, "groupArtifact"); |
| 69 | + if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) { |
| 70 | + throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'"); |
| 71 | + } |
| 72 | + Objects.requireNonNull(version, "version"); |
| 73 | + Objects.requireNonNull(style, "style"); |
| 74 | + Objects.requireNonNull(provisioner, "provisioner"); |
| 75 | + return FormatterStep.createLazy(NAME, |
| 76 | + () -> new State(NAME, groupArtifact, version, style, provisioner), |
| 77 | + State::createFormat); |
| 78 | + } |
| 79 | + |
| 80 | + static final Jvm.Support<String> JVM_SUPPORT = Jvm.<String> support(NAME).add(8, "1.1.0").add(11, "2.10.0"); |
| 81 | + |
| 82 | + public static String defaultGroupArtifact() { |
| 83 | + return MAVEN_COORDINATE; |
| 84 | + } |
| 85 | + |
| 86 | + /** Get default formatter version */ |
| 87 | + public static String defaultVersion() { |
| 88 | + return JVM_SUPPORT.getRecommendedFormatterVersion(); |
| 89 | + } |
| 90 | + |
| 91 | + public static String defaultStyle() { |
| 92 | + return DEFAULT_STYLE; |
| 93 | + } |
| 94 | + |
| 95 | + static final class State implements Serializable { |
| 96 | + private static final long serialVersionUID = 1L; |
| 97 | + |
| 98 | + /** The jar that contains the formatter. */ |
| 99 | + final JarState jarState; |
| 100 | + final String stepName; |
| 101 | + final String version; |
| 102 | + final String style; |
| 103 | + |
| 104 | + State(String stepName, String version, Provisioner provisioner) throws Exception { |
| 105 | + this(stepName, version, DEFAULT_STYLE, provisioner); |
| 106 | + } |
| 107 | + |
| 108 | + State(String stepName, String version, String style, Provisioner provisioner) throws Exception { |
| 109 | + this(stepName, MAVEN_COORDINATE, version, style, provisioner); |
| 110 | + } |
| 111 | + |
| 112 | + State(String stepName, String groupArtifact, String version, String style, Provisioner provisioner) throws Exception { |
| 113 | + JVM_SUPPORT.assertFormatterSupported(version); |
| 114 | + this.jarState = JarState.from(groupArtifact + ":" + version, provisioner); |
| 115 | + this.stepName = stepName; |
| 116 | + this.version = version; |
| 117 | + this.style = style; |
| 118 | + } |
| 119 | + |
| 120 | + @SuppressWarnings({"unchecked", "rawtypes"}) |
| 121 | + FormatterFunc createFormat() throws Exception { |
| 122 | + ClassLoader classLoader = jarState.getClassLoader(); |
| 123 | + |
| 124 | + // instantiate the formatter and get its format method |
| 125 | + Class<?> optionsClass = classLoader.loadClass(OPTIONS_CLASS); |
| 126 | + Class<?> optionsBuilderClass = classLoader.loadClass(OPTIONS_BUILDER_CLASS); |
| 127 | + Method optionsBuilderMethod = optionsClass.getMethod(OPTIONS_BUILDER_METHOD); |
| 128 | + Object optionsBuilder = optionsBuilderMethod.invoke(null); |
| 129 | + |
| 130 | + Class<?> optionsStyleClass = classLoader.loadClass(OPTIONS_Style); |
| 131 | + Object styleConstant = Enum.valueOf((Class<Enum>) optionsStyleClass, style); |
| 132 | + Method optionsBuilderStyleMethod = optionsBuilderClass.getMethod(OPTIONS_BUILDER_STYLE_METHOD, optionsStyleClass); |
| 133 | + optionsBuilderStyleMethod.invoke(optionsBuilder, styleConstant); |
| 134 | + |
| 135 | + Method optionsBuilderBuildMethod = optionsBuilderClass.getMethod(OPTIONS_BUILDER_BUILD_METHOD); |
| 136 | + Object options = optionsBuilderBuildMethod.invoke(optionsBuilder); |
| 137 | + |
| 138 | + Class<?> formatterClazz = classLoader.loadClass(FORMATTER_CLASS); |
| 139 | + Object formatter = formatterClazz.getMethod("createFormatter", optionsClass).invoke(null, options); |
| 140 | + Method formatterMethod = formatterClazz.getMethod(FORMATTER_METHOD, String.class); |
| 141 | + |
| 142 | + Function<String, String> removeUnused = constructRemoveUnusedFunction(classLoader); |
| 143 | + |
| 144 | + Class<?> importOrdererClass = classLoader.loadClass(IMPORT_ORDERER_CLASS); |
| 145 | + Method importOrdererMethod = importOrdererClass.getMethod(IMPORT_ORDERER_METHOD, String.class); |
| 146 | + |
| 147 | + return JVM_SUPPORT.suggestLaterVersionOnError(version, (input -> { |
| 148 | + String formatted = (String) formatterMethod.invoke(formatter, input); |
| 149 | + String removedUnused = removeUnused.apply(formatted); |
| 150 | + String sortedImports = (String) importOrdererMethod.invoke(null, removedUnused); |
| 151 | + return sortedImports; |
| 152 | + })); |
| 153 | + } |
| 154 | + |
| 155 | + FormatterFunc createRemoveUnusedImportsOnly() throws Exception { |
| 156 | + ClassLoader classLoader = jarState.getClassLoader(); |
| 157 | + Function<String, String> removeUnused = constructRemoveUnusedFunction(classLoader); |
| 158 | + return JVM_SUPPORT.suggestLaterVersionOnError(version, removeUnused::apply); |
| 159 | + } |
| 160 | + |
| 161 | + private static Function<String, String> constructRemoveUnusedFunction(ClassLoader classLoader) |
| 162 | + throws NoSuchMethodException, ClassNotFoundException { |
| 163 | + Class<?> removeUnusedClass = classLoader.loadClass(REMOVE_UNUSED_CLASS); |
| 164 | + Method removeUnusedMethod = removeUnusedClass.getMethod(REMOVE_UNUSED_METHOD, String.class); |
| 165 | + return (x) -> (String) removeUnusedMethod.invoke(null, x); |
| 166 | + } |
| 167 | + } |
| 168 | +} |
0 commit comments