Skip to content

Commit

Permalink
Fix YAML and JSON tests
Browse files Browse the repository at this point in the history
  • Loading branch information
blacelle committed Jan 12, 2023
1 parent fb3bb16 commit 13d9d3c
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,6 @@ nbdist/
nbactions.xml
nb-configuration.xml
.nb-gradle/

# MacOS jenv
.java-version
1 change: 0 additions & 1 deletion .java-version

This file was deleted.

6 changes: 5 additions & 1 deletion lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ def NEEDS_GLUE = [
'ktlint',
'flexmark',
'diktat',
'scalafmt'
'scalafmt',
'jackson'
]
for (glue in NEEDS_GLUE) {
sourceSets.register(glue) {
Expand Down Expand Up @@ -55,6 +56,9 @@ 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.dataformat:jackson-dataformat-yaml:2.13.4'

String VER_KTFMT = '0.42'
ktfmtCompileOnly "com.facebook:ktfmt:$VER_KTFMT"
String VER_KTLINT_GOOGLE_JAVA_FORMAT = '1.7' // for JDK 8 compatibility
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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.yaml;

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

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.diffplug.spotless.FormatterFunc;

public class YamlJacksonFormatterFunc implements FormatterFunc {
private List<String> enabledFeatures;
private List<String> disabledFeatures;

// private static final Logger logger = LoggerFactory.getLogger(YamlJacksonFormatterFunc.class);

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();

return format(objectMapper, input);
}

protected ObjectMapper makeObjectMapper() {
YAMLFactory yamlFactory = new YAMLFactory();
ObjectMapper objectMapper = new ObjectMapper(yamlFactory);

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

objectMapper.enable(feature);
}

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;
}

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("---")) {
// input = "---" + "\n" + input;
// }

try {
// https://stackoverflow.com/questions/25222327/deserialize-pojos-from-multiple-yaml-documents-in-a-single-file-in-jackson
// https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-375328648
// 2023-01: For now, we get 'Cannot deserialize value of type `com.fasterxml.jackson.databind.node.ObjectNode` from Array value'
// JsonParser yamlParser = objectMapper.getFactory().createParser(input);
// 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);
} catch (JsonProcessingException e) {
throw new AssertionError("Unable to format YAML. input='" + input + "'", e);
}
}

// Spotbugs
private static class ObjectNodeTypeReference extends TypeReference<ObjectNode> {}
}
31 changes: 21 additions & 10 deletions lib/src/main/java/com/diffplug/spotless/yaml/YamlJacksonStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
/**
* Simple YAML formatter which reformats the file according to Jackson YAMLFactory.
*/
// https://stackoverflow.com/questions/14515994/convert-json-string-to-pretty-print-json-output-using-jackson
public final class YamlJacksonStep {
private static final String MAVEN_COORDINATE = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:";
private static final String DEFAULT_VERSION = "2.13.4";
static final String MAVEN_COORDINATE = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:";
static final String DEFAULT_VERSION = "2.13.4";

public static String defaultVersion() {
return DEFAULT_VERSION;
Expand Down Expand Up @@ -80,8 +81,8 @@ FormatterFunc toFormatter() {
Method enableFeature;
Method disableFeature;

Method stringToObject;
Method objectToString;
Method stringToNode;
Method nodeToString;
try {
ClassLoader classLoader = jarState.getClassLoader();
jsonFactoryClass = classLoader.loadClass("com.fasterxml.jackson.core.JsonFactory");
Expand All @@ -97,8 +98,18 @@ FormatterFunc toFormatter() {
disableFeature = objectMapperClass.getMethod("disable", serializationFeatureClass);
}

stringToObject = objectMapperClass.getMethod("readValue", String.class, Class.class);
objectToString = objectMapperClass.getMethod("writeValueAsString", Object.class);
// https://stackoverflow.com/questions/25222327/deserialize-pojos-from-multiple-yaml-documents-in-a-single-file-in-jackson
// List<ObjectNode> docs = mapper
// .readValues<ObjectNode>(yamlParser, new TypeReference<ObjectNode> {})
// .readAll();

Class<?> jsonNodeClass = classLoader.loadClass("com.fasterxml.jackson.databind.JsonNode");

// This will transit with a JsonNode
// A JsonNode may keep the comments from the input node
stringToNode = objectMapperClass.getMethod("readTree", String.class);
// Not 'toPrettyString' as one could require no INDENT_OUTPUT
nodeToString = jsonNodeClass.getMethod("toPrettyString");
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new IllegalStateException("There was a problem preparing org.json dependencies", e);
}
Expand All @@ -125,15 +136,15 @@ FormatterFunc toFormatter() {
disableFeature.invoke(objectMapper, indentOutput);
}

return format(objectMapper, stringToObject, objectToString, s);
return format(objectMapper, stringToNode, nodeToString, s);
};
}

private String format(Object objectMapper, Method stringToObject, Method objectToString, String s)
private String format(Object objectMapper, Method stringToNode, Method nodeToString, String s)
throws IllegalAccessException, IllegalArgumentException {
try {
Object parsed = stringToObject.invoke(objectMapper, s, Object.class);
return (String) objectToString.invoke(objectMapper, parsed);
Object node = stringToNode.invoke(objectMapper, s);
return (String) nodeToString.invoke(node);
} catch (InvocationTargetException ex) {
throw new AssertionError("Unable to format YAML", ex.getCause());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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.yaml;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;

public class YamlJacksonV2Step {
private YamlJacksonV2Step() {}

public static String defaultVersion() {
return YamlJacksonStep.DEFAULT_VERSION;
}

public static FormatterStep create(List<String> enabledFeatures,
List<String> disabledFeatures,
String jacksonVersion,
Provisioner provisioner) {
Objects.requireNonNull(provisioner, "provisioner cannot be null");
return FormatterStep.createLazy("yaml",
() -> new State(enabledFeatures, disabledFeatures, jacksonVersion, provisioner),
State::toFormatter);
}

public static FormatterStep create(Provisioner provisioner) {
return create(Arrays.asList("INDENT_OUTPUT"), Arrays.asList(), defaultVersion(), provisioner);
}

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

private final List<String> enabledFeatures;
private final List<String> disabledFeatures;

private final JarState jarState;

private State(List<String> enabledFeatures,
List<String> disabledFeatures,
String jacksonVersion,
Provisioner provisioner) throws IOException {
this.enabledFeatures = enabledFeatures;
this.disabledFeatures = disabledFeatures;

this.jarState = JarState.from(YamlJacksonStep.MAVEN_COORDINATE + jacksonVersion, provisioner);
}

FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException {
Class<?> formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.yaml.YamlJacksonFormatterFunc");
Constructor<?> constructor = formatterFunc.getConstructor(List.class, List.class);
return (FormatterFunc) constructor.newInstance(enabledFeatures, disabledFeatures);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.diffplug.spotless.maven.FormatterStepConfig;
import com.diffplug.spotless.maven.FormatterStepFactory;
import com.diffplug.spotless.yaml.YamlJacksonStep;
import com.diffplug.spotless.yaml.YamlJacksonV2Step;

public class Jackson implements FormatterStepFactory {

Expand All @@ -40,7 +41,7 @@ public class Jackson implements FormatterStepFactory {
public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) {
List<String> enabledFeaturesAsList = Arrays.asList(enabledFeatures);
List<String> disabledFeaturesAsList = Arrays.asList(disabledFeatures);
return YamlJacksonStep
return YamlJacksonV2Step
.create(enabledFeaturesAsList, disabledFeaturesAsList, version, stepConfig.getProvisioner());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,29 @@
package com.diffplug.spotless.maven.json;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.maven.MavenIntegrationHarness;

public class JsonTest extends MavenIntegrationHarness {
private static final Logger LOGGER = LoggerFactory.getLogger(JsonTest.class);

@Test
public void testFormatJson_WithSimple_defaultConfig_sortByKeys() throws Exception {
writePomWithJsonSteps("<simple/>");

setFile("json_test.json").toResource("json/sortByKeysBefore.json");
mavenRunner().withArguments("spotless:apply").runNoError().error();
assertFile("json_test.json").sameAsResource("json/sortByKeysAfterDisabled.json");
mavenRunner().withArguments("spotless:apply").runNoError();
assertFile("json_test.json").sameAsResource("json/sortByKeysAfterDisabled_Simple.json");
}

@Test
public void testFormatJson_WithSimple_defaultConfig_nestedObject() throws Exception {
writePomWithJsonSteps("<simple/>");

setFile("json_test.json").toResource("json/nestedObjectBefore.json");
mavenRunner().withArguments("spotless:apply").runNoError().error();
mavenRunner().withArguments("spotless:apply").runNoError();
assertFile("json_test.json").sameAsResource("json/nestedObjectAfter.json");
}

Expand All @@ -43,7 +47,7 @@ public void testFormatJson_WithGson_defaultConfig_sortByKeys() throws Exception
writePomWithJsonSteps("<gson/>");

setFile("json_test.json").toResource("json/sortByKeysBefore.json");
mavenRunner().withArguments("spotless:apply").runNoError().error();
mavenRunner().withArguments("spotless:apply").runNoError();
assertFile("json_test.json").sameAsResource("json/sortByKeysAfterDisabled.json");
}

Expand All @@ -52,8 +56,20 @@ public void testFormatJson_WithGson_sortByKeys() throws Exception {
writePomWithJsonSteps("<gson><sortByKeys>true</sortByKeys></gson>");

setFile("json_test.json").toResource("json/sortByKeysBefore.json");
mavenRunner().withArguments("spotless:apply").runNoError().error();

String output = mavenRunner().withArguments("spotless:apply").runNoError().output();
LOGGER.error(output);
System.err.println(output);
assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json");
}

@Test
public void testFormatJson_WithGson_defaultConfig_nestedObject() throws Exception {
writePomWithJsonSteps("<gson/>");

setFile("json_test.json").toResource("json/nestedObjectBefore.json");
mavenRunner().withArguments("spotless:apply").runNoError();
assertFile("json_test.json").sameAsResource("json/nestedObjectAfter.json");
}

}
Loading

0 comments on commit 13d9d3c

Please sign in to comment.