Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored Eclipse formatter configuration and step creation #253

Merged
merged 18 commits into from
Jul 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ You might be looking for:

### Version 1.14.0-SNAPSHOT - TBD (javadoc [lib](https://diffplug.github.io/spotless/javadoc/spotless-lib/snapshot/) [lib-extra](https://diffplug.github.io/spotless/javadoc/spotless-lib-extra/snapshot/), [snapshot repo](https://oss.sonatype.org/content/repositories/snapshots/com/diffplug/spotless/))

* Eclipse formatter versions decoupled from Spotless formatter step implementations to allow independent updates of M2 based Eclipse dependencies. ([#253](https://github.com/diffplug/spotless/pull/253))

### Version 1.13.0 - June 1st 2018 (javadoc [lib](https://diffplug.github.io/spotless/javadoc/spotless-lib/1.11.0/) [lib-extra](https://diffplug.github.io/spotless/javadoc/spotless-lib-extra/1.13.0/), artifact [lib]([jcenter](https://bintray.com/diffplug/opensource/spotless-lib), [lib-extra]([jcenter](https://bintray.com/diffplug/opensource/spotless-lib-extra)))

* Add line and column numbers to ktlint errors. ([#251](https://github.com/diffplug/spotless/pull/251))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* 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.extra;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

import com.diffplug.common.base.Errors;
import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterProperties;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.ThrowingEx;

/**
* Generic Eclipse based formatter step {@link State} builder.
*/
public class EclipseBasedStepBuilder {
private final String formatterName;
private final ThrowingEx.Function<State, FormatterFunc> stateToFormatter;
private final Provisioner jarProvisioner;

/**
* Resource location of Spotless Eclipse Formatter Maven coordinate lists.
* <p>
* Spotless Eclipse Formatter dependencies have fixed transitive versions, since Spotless Eclipse Formatter
* implementations access internal methods of the Eclipse plugins, which may change with every
* version change, including minor and patch version changes.
* At the resource location for each supported Spotless Eclipse Formatter, a text file is provided, containing
* the fixed versions for the formatter and its transitive dependencies.
* Each line is either a comment starting with {@code #} or corresponds to the format
* {@code <groupId>:<artifactId>[:packaging][:classifier]:<versionRestriction>}
* </p>
*/
private static final String ECLIPSE_FORMATTER_RESOURCES = EclipseBasedStepBuilder.class.getPackage().getName().replace('.', '/');

private List<String> dependencies = new ArrayList<>();
private Iterable<File> settingsFiles = new ArrayList<>();

/** Initialize valid default configuration, taking latest version */
public EclipseBasedStepBuilder(String formatterName, Provisioner jarProvisioner, ThrowingEx.Function<State, FormatterFunc> stateToFormatter) {
this.formatterName = Objects.requireNonNull(formatterName, "formatterName");
this.jarProvisioner = Objects.requireNonNull(jarProvisioner, "jarProvisioner");
this.stateToFormatter = Objects.requireNonNull(stateToFormatter, "stateToFormatter");
}

/** Returns the FormatterStep (whose state will be calculated lazily). */
public FormatterStep build() {
return FormatterStep.createLazy(formatterName, this::get, stateToFormatter);
}

/** Set dependencies for the corresponding Eclipse version */
public void setVersion(String version) {
String url = "/" + ECLIPSE_FORMATTER_RESOURCES + "/" + formatterName.replace(' ', '_') + "/v" + version + ".lockfile";
InputStream depsFile = EclipseBasedStepBuilder.class.getResourceAsStream(url);
if (depsFile == null) {
throw new IllegalArgumentException("No such version " + version + ", expected at " + url);
}
byte[] content = toByteArray(depsFile);
String allLines = new String(content, StandardCharsets.UTF_8);
String[] lines = allLines.split("\n");
dependencies.clear();
for (String line : lines) {
if (!line.startsWith("#")) {
dependencies.add(line);
}
}
}

private static byte[] toByteArray(InputStream in) {
ByteArrayOutputStream to = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
try {
while (true) {
int r = in.read(buf);
if (r == -1) {
break;
}
to.write(buf, 0, r);
}
return to.toByteArray();
} catch (IOException e) {
throw Errors.asRuntime(e);
}
}

/** Set settings files containing Eclipse preferences */
public void setPreferences(Iterable<File> settingsFiles) {
this.settingsFiles = settingsFiles;
}

/** Creates the state of the configuration. */
EclipseBasedStepBuilder.State get() throws IOException {
/*
* The current use case is tailored for Gradle.
* Gradle calls this method only once per execution
* and compares the State with the one of a previous run
* for incremental building.
* Hence a lazy construction is not required.
*/
return new State(
jarProvisioner,
dependencies,
settingsFiles);
}

/**
* State of Eclipse configuration items, providing functionality to derived information
* based on the state.
*/
public static class State implements Serializable {
// Not used, only the serialization output is required to determine whether the object has changed
private static final long serialVersionUID = 1L;

private final JarState jarState;
private final FileSignature settingsFiles;

/** State constructor expects that all passed items are not modified afterwards */
protected State(Provisioner jarProvisioner, List<String> dependencies, Iterable<File> settingsFiles) throws IOException {
this.jarState = JarState.from(dependencies, jarProvisioner);
this.settingsFiles = FileSignature.signAsList(settingsFiles);
}

/** Get formatter preferences */
public Properties getPreferences() {
//Keep the IllegalArgumentException since it contains detailed information
FormatterProperties preferences = FormatterProperties.from(settingsFiles.files());
return preferences.getProperties();
}

/** Load class based on the given configuration of JAR provider and Maven coordinates. */
public Class<?> loadClass(String name) {
try {
return jarState.getClassLoader().loadClass(name);
} catch (ClassNotFoundException e) {
throw Errors.asRuntime(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@ReturnValuesAreNonnullByDefault
package com.diffplug.spotless.extra.config;

import javax.annotation.ParametersAreNonnullByDefault;

import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault;
Original file line number Diff line number Diff line change
Expand Up @@ -16,76 +16,62 @@
package com.diffplug.spotless.extra.groovy;

import java.io.File;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Properties;

import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterProperties;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.extra.EclipseBasedStepBuilder;

/** Formatter step which calls out to the Groovy-Eclipse formatter. */
public class GrEclipseFormatterStep {
private static final String NAME = "groovy-eclipse formatter";
public final class GrEclipseFormatterStep {
// prevent direct instantiation
private GrEclipseFormatterStep() {}

private static final String NAME = "groovy eclipse formatter";
private static final String FORMATTER_CLASS = "com.diffplug.gradle.spotless.groovy.eclipse.GrEclipseFormatterStepImpl";
private static final String DEFAULT_VERSION = "2.3.0";
private static final String MAVEN_COORDINATE = "com.diffplug.spotless:spotless-ext-greclipse:";
private static final String DEFAULT_VERSION = "4.6.3";
private static final String FORMATTER_METHOD = "format";

/** Creates a formatter step using the default version for the given settings file. */
@Deprecated
public static FormatterStep create(Iterable<File> settingsFiles, Provisioner provisioner) {
return create(defaultVersion(), settingsFiles, provisioner);
}

/** Creates a formatter step for the given version and settings file. */
@Deprecated
public static FormatterStep create(String version, Iterable<File> settingsFiles, Provisioner provisioner) {
return FormatterStep.createLazy(
NAME,
() -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), settingsFiles),
State::createFormat);
EclipseBasedStepBuilder builder = createBuilder(provisioner);
builder.setVersion(version);
builder.setPreferences(settingsFiles);
return builder.build();
}

public static String defaultVersion() {
return DEFAULT_VERSION;
}

private static class State implements Serializable {
private static final long serialVersionUID = 1L;

/** The jar that contains the eclipse formatter. */
final JarState jarState;
/** The signature of the settings file. */
final FileSignature settings;

State(JarState jar, final Iterable<File> settingsFiles) throws Exception {
this.jarState = Objects.requireNonNull(jar);
this.settings = FileSignature.signAsList(settingsFiles);
}

FormatterFunc createFormat() throws Exception {
FormatterProperties preferences = FormatterProperties.from(settings.files());

ClassLoader classLoader = jarState.getClassLoader();
/** Provides default configuration */
public static EclipseBasedStepBuilder createBuilder(Provisioner provisioner) {
return new EclipseBasedStepBuilder(NAME, provisioner, GrEclipseFormatterStep::apply);
}

// instantiate the formatter and get its format method
Class<?> formatterClazz = classLoader.loadClass(FORMATTER_CLASS);
Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(preferences.getProperties());
Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class);
return input -> {
try {
return (String) method.invoke(formatter, input);
} catch (InvocationTargetException exceptionWrapper) {
Throwable throwable = exceptionWrapper.getTargetException();
Exception exception = (throwable instanceof Exception) ? (Exception) throwable : null;
throw (null == exception) ? exceptionWrapper : exception;
}
};
}
private static FormatterFunc apply(EclipseBasedStepBuilder.State state) throws Exception {
Class<?> formatterClazz = state.loadClass(FORMATTER_CLASS);
Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences());
Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class);
return input -> {
try {
return (String) method.invoke(formatter, input);
} catch (InvocationTargetException exceptionWrapper) {
Throwable throwable = exceptionWrapper.getTargetException();
Exception exception = (throwable instanceof Exception) ? (Exception) throwable : null;
throw (null == exception) ? exceptionWrapper : exception;
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,23 @@
package com.diffplug.spotless.extra.java;

import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Objects;
import java.util.Properties;

import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterProperties;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.extra.EclipseBasedStepBuilder;

/** Formatter step which calls out to the Eclipse formatter. */
/**
* Formatter step which calls out to the Eclipse formatter.
* This class is deprecated. Use {@link EclipseJdtFormatterStep} instead.
*/
@Deprecated
public final class EclipseFormatterStep {
// prevent direct instantiation
private EclipseFormatterStep() {}

private static final String DEFAULT_VERSION = "4.7.2";
private static final String NAME = "eclipse formatter";
private static final String MAVEN_COORDINATE = "com.diffplug.spotless:spotless-ext-eclipse-jdt:";
private static final String FORMATTER_CLASS = "com.diffplug.gradle.spotless.java.eclipse.EclipseFormatterStepImpl";
private static final String FORMATTER_METHOD = "format";

/** Creates a formatter step for the given version and settings file.
* Formatter steps based on property configuration should support zero (default configuration)
Expand Down Expand Up @@ -66,39 +60,14 @@ public static FormatterStep create(String version, Iterable<File> settingsFiles,
Objects.requireNonNull(version, "version");
Objects.requireNonNull(settingsFiles, "settingsFiles");
Objects.requireNonNull(provisioner, "provisioner");
return FormatterStep.createLazy(NAME,
() -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), settingsFiles),
State::createFormat);
EclipseBasedStepBuilder builder = EclipseJdtFormatterStep.createBuilder(provisioner);
builder.setPreferences(settingsFiles);
builder.setVersion(DEFAULT_VERSION);
return builder.build();
}

public static String defaultVersion() {
return DEFAULT_VERSION;
}

private static class State implements Serializable {
private static final long serialVersionUID = 1L;

/** The jar that contains the eclipse formatter. */
final JarState jarState;
/** The signature of the settings file. */
final FileSignature settings;

State(JarState jar, final Iterable<File> settingsFiles) throws Exception {
this.jarState = jar;
this.settings = FileSignature.signAsList(settingsFiles);
}

FormatterFunc createFormat() throws Exception {
FormatterProperties preferences = FormatterProperties.from(settings.files());

ClassLoader classLoader = jarState.getClassLoader();

// instantiate the formatter and get its format method
Class<?> formatterClazz = classLoader.loadClass(FORMATTER_CLASS);
Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(preferences.getProperties());
Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class);
return input -> (String) method.invoke(formatter, input);
}
}

}
Loading