/*
* Copyright 2016 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.kotlin;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Objects;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.ThrowingEx;
/** Wraps up [ktlint](https://github.com/shyiko/ktlint) as a FormatterStep. */
public class KtLintStep {
// prevent direct instantiation
private KtLintStep() {}
private static final String DEFAULT_VERSION = "0.6.1";
static final String NAME = "ktlint";
static final String MAVEN_COORDINATE = "com.github.shyiko:ktlint:";
public static FormatterStep create(Provisioner provisioner) {
return create(defaultVersion(), provisioner);
}
public static FormatterStep create(String version, Provisioner provisioner) {
Objects.requireNonNull(version, "version");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.createLazy(NAME,
() -> new State(version, provisioner),
State::createFormat);
}
public static String defaultVersion() {
return DEFAULT_VERSION;
}
static final class State implements Serializable {
private static final long serialVersionUID = 1L;
/** The jar that contains the eclipse formatter. */
final JarState jarState;
State(String version, Provisioner provisioner) throws IOException {
this.jarState = JarState.from(MAVEN_COORDINATE + version, provisioner);
}
FormatterFunc createFormat() throws Exception {
ClassLoader classLoader = jarState.getClassLoader();
// String KtLint::format(String input, Iterable<RuleSet> rules, Function2 errorCallback)
// first, we get the standard rules
Class<?> standardRuleSetProviderClass = classLoader.loadClass("com.github.shyiko.ktlint.ruleset.standard.StandardRuleSetProvider");
Object standardRuleSet = standardRuleSetProviderClass.getMethod("get").invoke(standardRuleSetProviderClass.newInstance());
Iterable<?> ruleSets = Collections.singletonList(standardRuleSet);
// next, we create an error callback which throws an assertion error when the format is bad
Class<?> function2Interface = classLoader.loadClass("kotlin.jvm.functions.Function2");
Class<?> lintErrorClass = classLoader.loadClass("com.github.shyiko.ktlint.core.LintError");
Method detailGetter = lintErrorClass.getMethod("getDetail");
Object formatterCallback = Proxy.newProxyInstance(classLoader, new Class[]{function2Interface},
(proxy, method, args) -> {
Object lintError = args[0]; // com.github.shyiko.ktlint.core.LintError
boolean corrected = (Boolean) args[1];
if (!corrected) {
String detail = (String) detailGetter.invoke(lintError);
throw new AssertionError(detail);
}
return null;
});
// grab the KtLint singleton
Class<?> ktlintClass = classLoader.loadClass("com.github.shyiko.ktlint.core.KtLint");
Object ktlint = ktlintClass.getDeclaredField("INSTANCE").get(null);
// and its format method
Method formatterMethod = ktlintClass.getMethod("format", String.class, Iterable.class, function2Interface);
return input -> {
try {
String formatted = (String) formatterMethod.invoke(ktlint, input, ruleSets, formatterCallback);
return formatted;
} catch (InvocationTargetException e) {
throw ThrowingEx.unwrapCause(e);
}
};
}
}
}