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

Add JSON jackson step, Refactor with Yaml, enable endWithEol, feature… #1492

Closed
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
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 @@ -10,6 +10,8 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
### Changes
* Rename `YamlJacksonStep` into `JacksonYamlStep` while normalizing Jackson usage ([#1492](https://github.com/diffplug/spotless/pull/1492))

## [2.32.0] - 2023-01-13
### Added
Expand Down
1 change: 1 addition & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ dependencies {
palantirJavaFormatCompileOnly 'com.palantir.javaformat:palantir-java-format:1.1.0' // this version needs to stay compilable against Java 8 for CI Job testNpm

// used jackson-based formatters
jacksonCompileOnly 'com.fasterxml.jackson.core:jackson-databind:2.14.1'
jacksonCompileOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.1'

String VER_KTFMT = '0.42'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021-2023 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.glue.json;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.json.JacksonConfig;

/**
* A {@link FormatterFunc} based on Jackson library
*/
// https://github.com/FasterXML/jackson-dataformats-text/issues/372
public abstract class AJacksonFormatterFunc implements FormatterFunc {
private JacksonConfig jacksonConfig;

public AJacksonFormatterFunc(JacksonConfig jacksonConfig) {
this.jacksonConfig = jacksonConfig;
}

@Override
public String apply(String input) throws Exception {
ObjectMapper objectMapper = makeObjectMapper();

return format(objectMapper, input);
}

protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException {
try {
ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class);
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode);
} catch (JsonProcessingException e) {
throw new AssertionError("Unable to format. input='" + input + "'", e);
}
}

/**
* @return a {@link JsonFactory}. May be overridden to handle alternative formats.
* @see <a href="https://github.com/FasterXML/jackson-dataformats-text">jackson-dataformats-text</a>
*/
protected abstract JsonFactory makeJsonFactory();

protected ObjectMapper makeObjectMapper() {
JsonFactory jsonFactory = makeJsonFactory();
ObjectMapper objectMapper = new ObjectMapper(jsonFactory);

objectMapper.setDefaultPrettyPrinter(makePrinter());

// Configure the ObjectMapper
// https://github.com/FasterXML/jackson-databind#commonly-used-features
jacksonConfig.getFeatureToToggle().forEach((rawFeature, toggle) -> {
// https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection
SerializationFeature feature = SerializationFeature.valueOf(rawFeature);

objectMapper.configure(feature, toggle);
});

new JsonFactory().configure(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT, true);

return objectMapper;
}

protected DefaultPrettyPrinter makePrinter() {
return new DefaultPrettyPrinter();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2021-2023 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.glue.json;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.core.util.Separators;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.json.JacksonJsonConfig;

/**
* A {@link FormatterFunc} based on Jackson library
*/
// https://github.com/FasterXML/jackson-dataformats-text/issues/372
public class JacksonJsonFormatterFunc extends AJacksonFormatterFunc {
private JacksonJsonConfig jacksonConfig;

public JacksonJsonFormatterFunc(JacksonJsonConfig jacksonConfig) {
super(jacksonConfig);
this.jacksonConfig = jacksonConfig;
}

/**
* @return a {@link JsonFactory}. May be overridden to handle alternative formats.
* @see <a href="https://github.com/FasterXML/jackson-dataformats-text">jackson-dataformats-text</a>
*/
protected JsonFactory makeJsonFactory() {
JsonFactory jsonFactory = new JsonFactoryBuilder().build();

// Configure the ObjectMapper
// https://github.com/FasterXML/jackson-databind#commonly-used-features
jacksonConfig.getJsonFeatureToToggle().forEach((rawFeature, toggle) -> {
// https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection
JsonGenerator.Feature feature = JsonGenerator.Feature.valueOf(rawFeature);

jsonFactory.configure(feature, toggle);
});

return jsonFactory;
}

@Override
protected DefaultPrettyPrinter makePrinter() {
boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator();

// DefaultIndenter default constructor relies on 2 whitespaces as default tabulation
// By we want to force '\n' as eol given Spotless provides LF-input (whatever the actual File content/current OS)
DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(" ", "\n");
DefaultPrettyPrinter printer = new SpotlessJsonPrettyPrinter(spaceBeforeSeparator);

printer.indentObjectsWith(indenter);
printer.indentArraysWith(indenter);
return printer;
}

protected static class SpotlessJsonPrettyPrinter extends DefaultPrettyPrinter {
private static final long serialVersionUID = 1L;
private final boolean spaceBeforeSeparator;

public SpotlessJsonPrettyPrinter(boolean spaceBeforeSeparator) {
this.spaceBeforeSeparator = spaceBeforeSeparator;
}

@Override
public DefaultPrettyPrinter createInstance() {
return new SpotlessJsonPrettyPrinter(spaceBeforeSeparator);
}

@Override
public DefaultPrettyPrinter withSeparators(Separators separators) {
this._separators = separators;
if (spaceBeforeSeparator) {
// This is Jackson default behavior
this._objectFieldValueSeparatorWithSpaces = " " + separators.getObjectFieldValueSeparator() + " ";
} else {
this._objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
}
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,46 @@
package com.diffplug.spotless.glue.yaml;

import java.io.IOException;
import java.util.List;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.glue.json.AJacksonFormatterFunc;
import com.diffplug.spotless.yaml.JacksonYamlConfig;

public class YamlJacksonFormatterFunc implements FormatterFunc {
private List<String> enabledFeatures;
private List<String> disabledFeatures;
public class JacksonYamlFormatterFunc extends AJacksonFormatterFunc {
final JacksonYamlConfig yamlConfig;

public YamlJacksonFormatterFunc(List<String> enabledFeatures, List<String> disabledFeatures) {
this.enabledFeatures = enabledFeatures;
this.disabledFeatures = disabledFeatures;
}

@Override
public String apply(String input) throws Exception {
ObjectMapper objectMapper = makeObjectMapper();
public JacksonYamlFormatterFunc(JacksonYamlConfig jacksonConfig) {
super(jacksonConfig);
this.yamlConfig = jacksonConfig;

return format(objectMapper, input);
if (jacksonConfig == null) {
throw new IllegalArgumentException("ARG");
}
}

protected ObjectMapper makeObjectMapper() {
YAMLFactory yamlFactory = new YAMLFactory();
ObjectMapper objectMapper = new ObjectMapper(yamlFactory);
protected JsonFactory makeJsonFactory() {
YAMLFactoryBuilder yamlFactoryBuilder = new YAMLFactoryBuilder(new YAMLFactory());

// Configure the ObjectMapper
// https://github.com/FasterXML/jackson-databind#commonly-used-features
for (String rawFeature : enabledFeatures) {
yamlConfig.getYamlFeatureToToggle().forEach((rawFeature, toggle) -> {
// https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection
SerializationFeature feature = SerializationFeature.valueOf(rawFeature);
YAMLGenerator.Feature feature = YAMLGenerator.Feature.valueOf(rawFeature);

objectMapper.enable(feature);
}
yamlFactoryBuilder.configure(feature, toggle);
});

for (String rawFeature : disabledFeatures) {
// https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection
SerializationFeature feature = SerializationFeature.valueOf(rawFeature);

objectMapper.disable(feature);
}
return objectMapper;
return yamlFactoryBuilder.build();
}

@Override
protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException {
// We may consider adding manually an initial '---' prefix to help management of multiple documents
// if (!input.trim().startsWith("---")) {
Expand All @@ -79,19 +70,16 @@ protected String format(ObjectMapper objectMapper, String input) throws IllegalA
// List<ObjectNode> docs = objectMapper.readValues(yamlParser, ObjectNode.class).readAll();
// return objectMapper.writeValueAsString(docs);

// 2023-01: This returns JSON instead of YAML
// This will transit with a JsonNode
// A JsonNode may keep the comments from the input node
// JsonNode jsonNode = objectMapper.readTree(input);
//Not 'toPrettyString' as one could require no INDENT_OUTPUT
// return jsonNode.toPrettyString();
ObjectNode objectNode = objectMapper.readValue(input, ObjectNode.class);
return objectMapper.writeValueAsString(objectNode);
String outputFromjackson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode);

return outputFromjackson;
} catch (JsonProcessingException e) {
throw new AssertionError("Unable to format YAML. input='" + input + "'", e);
throw new AssertionError("Unable to format. input='" + input + "'", e);
}
}

// Spotbugs
private static class ObjectNodeTypeReference extends TypeReference<ObjectNode> {}
}
52 changes: 52 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2023 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.json;

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* A DTO holding the basic for Jackson-based formatters
*/
public class JacksonConfig implements Serializable {
private static final long serialVersionUID = 1L;

private static final Map<String, Boolean> DEFAULT_FEATURE_TOGGLES;

static {
Map<String, Boolean> defaultFeatureToggles = new LinkedHashMap<>();
// We activate by default the PrettyPrinter from Jackson
// @see com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT
defaultFeatureToggles.put("INDENT_OUTPUT", true);
DEFAULT_FEATURE_TOGGLES = defaultFeatureToggles;
}

protected Map<String, Boolean> featureToToggle = new LinkedHashMap<>();

public Map<String, Boolean> getFeatureToToggle() {
return Collections.unmodifiableMap(featureToToggle);
}

public void setFeatureToToggle(Map<String, Boolean> featureToToggle) {
this.featureToToggle = featureToToggle;
}

public void appendFeatureToToggle(Map<String, Boolean> features) {
this.featureToToggle.putAll(features);
}
}
Loading