[JOHNZON-350] record support in example to model mojo
diff --git a/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/ExampleToModelMojo.java b/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/ExampleToModelMojo.java
index bbdb590..2ce7ee7 100644
--- a/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/ExampleToModelMojo.java
+++ b/johnzon-maven-plugin/src/main/java/org/apache/johnzon/maven/plugin/ExampleToModelMojo.java
@@ -20,7 +20,6 @@
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
@@ -35,7 +34,6 @@
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
-import java.io.FilenameFilter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
@@ -53,7 +51,7 @@
@Mojo(name = "example-to-model", defaultPhase = GENERATE_SOURCES)
public class ExampleToModelMojo extends AbstractMojo {
// not strictly forbidden but kind of file to java convertion
- private static final List<Character> FORBIDDEN_JAVA_NAMES = asList('-', '_', '.');
+ private static final List<Character> FORBIDDEN_JAVA_NAMES = asList('-', '_', '.', '{', '}');
@Parameter(property = "johnzon.source", defaultValue = "${project.basedir}/src/main/johnzon")
protected File source;
@@ -73,18 +71,22 @@
@Parameter(property = "johnzon.attach", defaultValue = "true")
protected boolean attach;
+ @Parameter(property = "johnzon.useRecord", defaultValue = "false")
+ protected boolean useRecord;
+
+ @Parameter(property = "johnzon.useJsonb", defaultValue = "false")
+ protected boolean useJsonb;
+
+ @Parameter(property = "johnzon.ignoreNull", defaultValue = "false")
+ protected boolean ignoreNull;
+
@Override
- public void execute() throws MojoExecutionException, MojoFailureException {
- final JsonReaderFactory readerFactory = Json.createReaderFactory(Collections.<String, Object>emptyMap());
+ public void execute() throws MojoExecutionException {
+ final JsonReaderFactory readerFactory = Json.createReaderFactory(Collections.emptyMap());
if (source.isFile()) {
generateFile(readerFactory, source);
} else {
- final File[] children = source.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(final File dir, final String name) {
- return name.endsWith(".json");
- }
- });
+ final File[] children = source.listFiles((dir, name) -> name.endsWith(".json"));
if (children == null || children.length == 0) {
throw new MojoExecutionException("No json file found in " + source);
}
@@ -99,16 +101,14 @@
// TODO: unicity of field name, better nested array/object handling
private void generate(final JsonReaderFactory readerFactory, final File source, final Writer writer, final String javaName) throws MojoExecutionException {
- JsonReader reader = null;
- try {
- reader = readerFactory.createReader(new FileReader(source));
+ try (final JsonReader reader = readerFactory.createReader(new FileReader(source))) {
final JsonStructure structure = reader.read();
if (JsonArray.class.isInstance(structure) || !JsonObject.class.isInstance(structure)) { // quite redundant for now but to avoid surprises in future
throw new MojoExecutionException("This plugin doesn't support array generation, generate the model (generic) and handle arrays in your code");
}
final JsonObject object = JsonObject.class.cast(structure);
- final Collection<String> imports = new TreeSet<String>();
+ final Collection<String> imports = new TreeSet<>();
// while we browse the example tree just store imports as well, avoids a 2 passes processing duplicating imports logic
final StringWriter memBuffer = new StringWriter();
@@ -128,53 +128,64 @@
writer.write('\n');
}
- writer.write("public class " + javaName + " {\n");
- writer.write(memBuffer.toString());
+ if (useRecord) {
+ writer.write("public record " + javaName + "(\n");
+ writer.write(memBuffer.toString());
+ } else {
+ writer.write("public class " + javaName + " {\n");
+ writer.write(memBuffer.toString());
+ }
writer.write("}\n");
} catch (final IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
- } finally {
- if (reader != null) {
- reader.close();
- }
}
}
- private void generateFieldsAndMethods(final Writer writer, final JsonObject object, final String prefix,
+ private void generateFieldsAndMethods(final StringWriter writer, final JsonObject object, final String prefix,
final Collection<String> imports) throws IOException {
- final Map<String, JsonObject> nestedTypes = new TreeMap<String, JsonObject>();
+ final Map<String, JsonObject> nestedTypes = new TreeMap<>();
{
final Iterator<Map.Entry<String, JsonValue>> iterator = object.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, JsonValue> entry = iterator.next();
final String key = entry.getKey();
final String fieldName = toJavaFieldName(key);
+ final boolean hasNext = iterator.hasNext();
switch (entry.getValue().getValueType()) {
case ARRAY:
imports.add("java.util.List");
- handleArray(writer, prefix, nestedTypes, entry.getValue(), key, fieldName, 1, imports);
+ handleArray(writer, prefix, nestedTypes, entry.getValue(), key, fieldName, 1, imports, !hasNext);
break;
case OBJECT:
final String type = toJavaName(fieldName);
nestedTypes.put(type, JsonObject.class.cast(entry.getValue()));
- fieldGetSetMethods(writer, key, fieldName, type, prefix, 0, imports);
+ fieldGetSetMethods(writer, key, fieldName, type, prefix, 0, imports, !hasNext);
break;
case TRUE:
case FALSE:
- fieldGetSetMethods(writer, key, fieldName, "Boolean", prefix, 0, imports);
+ fieldGetSetMethods(writer, key, fieldName, "Boolean", prefix, 0, imports, !hasNext);
break;
case NUMBER:
- fieldGetSetMethods(writer, key, fieldName, "Double", prefix, 0, imports);
+ fieldGetSetMethods(writer, key, fieldName, "Double", prefix, 0, imports, !hasNext);
break;
case STRING:
- fieldGetSetMethods(writer, key, fieldName, "String", prefix, 0, imports);
+ fieldGetSetMethods(writer, key, fieldName, "String", prefix, 0, imports, !hasNext);
break;
case NULL:
default:
- throw new UnsupportedOperationException("Unsupported " + entry.getValue() + ".");
+ if (ignoreNull) {
+ if (useRecord && writer.getBuffer().length() > 0) {
+ writer.getBuffer().setLength(writer.getBuffer().length() - 2);
+ writer.write("\n");
+ }
+ } else {
+ throw new UnsupportedOperationException("Unsupported " + entry.getValue() + ".");
+ }
}
- if (iterator.hasNext()) {
+ if (hasNext) {
writer.write("\n");
+ } else if (useRecord) {
+ writer.write(") {\n");
}
}
}
@@ -186,7 +197,11 @@
final Iterator<Map.Entry<String, JsonObject>> entries = nestedTypes.entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry<String, JsonObject> entry = entries.next();
- writer.write(prefix + "public static class " + entry.getKey() + " {\n");
+ if (useRecord) {
+ writer.write(prefix + "public static record " + entry.getKey() + "(\n");
+ } else {
+ writer.write(prefix + "public static class " + entry.getKey() + " {\n");
+ }
generateFieldsAndMethods(writer, entry.getValue(), " " + prefix, imports);
writer.write(prefix + "}\n");
if (entries.hasNext()) {
@@ -200,7 +215,8 @@
final JsonValue value,
final String jsonField, final String fieldName,
final int arrayLevel,
- final Collection<String> imports) throws IOException {
+ final Collection<String> imports,
+ final boolean last) throws IOException {
final JsonArray array = JsonArray.class.cast(value);
if (array.size() > 0) { // keep it simple for now - 1 level, we can have an awesome recursive algo later if needed
final JsonValue jsonValue = array.get(0);
@@ -208,20 +224,20 @@
case OBJECT:
final String javaName = toJavaName(fieldName);
nestedTypes.put(javaName, JsonObject.class.cast(jsonValue));
- fieldGetSetMethods(writer, jsonField, fieldName, javaName, prefix, arrayLevel, imports);
+ fieldGetSetMethods(writer, jsonField, fieldName, javaName, prefix, arrayLevel, imports, last);
break;
case TRUE:
case FALSE:
- fieldGetSetMethods(writer, jsonField, fieldName, "Boolean", prefix, arrayLevel, imports);
+ fieldGetSetMethods(writer, jsonField, fieldName, "Boolean", prefix, arrayLevel, imports, last);
break;
case NUMBER:
- fieldGetSetMethods(writer, jsonField, fieldName, "Double", prefix, arrayLevel, imports);
+ fieldGetSetMethods(writer, jsonField, fieldName, "Double", prefix, arrayLevel, imports, last);
break;
case STRING:
- fieldGetSetMethods(writer, jsonField, fieldName, "String", prefix, arrayLevel, imports);
+ fieldGetSetMethods(writer, jsonField, fieldName, "String", prefix, arrayLevel, imports, last);
break;
case ARRAY:
- handleArray(writer, prefix, nestedTypes, jsonValue, jsonField, fieldName, arrayLevel + 1, imports);
+ handleArray(writer, prefix, nestedTypes, jsonValue, jsonField, fieldName, arrayLevel + 1, imports, last);
break;
case NULL:
default:
@@ -235,23 +251,42 @@
private void fieldGetSetMethods(final Writer writer,
final String jsonField, final String field,
final String type, final String prefix, final int arrayLevel,
- final Collection<String> imports) throws IOException {
+ final Collection<String> imports, final boolean last) throws IOException {
final String actualType = buildArrayType(arrayLevel, type);
final String actualField = buildValidFieldName(jsonField);
final String methodName = capitalize(actualField);
- if (!jsonField.equals(field)) { // TODO: add it to imports in eager visitor
- imports.add("org.apache.johnzon.mapper.JohnzonProperty");
- writer.append(prefix).append("@JohnzonProperty(\"").append(jsonField).append("\")\n");
+ if (!jsonField.equals(field)) {
+ if (useJsonb) {
+ imports.add("javax.json.bind.annotation.JsonbProperty");
+ writer.append(prefix).append("@JsonbProperty(\"").append(jsonField).append("\")");
+ } else {
+ imports.add("org.apache.johnzon.mapper.JohnzonProperty");
+ writer.append(prefix).append("@JohnzonProperty(\"").append(jsonField).append("\")");
+ }
+ if (useRecord) {
+ writer.append(" ");
+ } else {
+ writer.append("\n").append(prefix);
+ }
+ } else {
+ writer.append(prefix);
}
-
- writer.append(prefix).append("private ").append(actualType).append(" ").append(actualField).append(";\n");
- writer.append(prefix).append("public ").append(actualType).append(" get").append(methodName).append("() {\n");
- writer.append(prefix).append(" return ").append(actualField).append(";\n");
- writer.append(prefix).append("}\n");
- writer.append(prefix).append("public void set").append(methodName).append("(final ").append(actualType).append(" newValue) {\n");
- writer.append(prefix).append(" this.").append(actualField).append(" = newValue;\n");
- writer.append(prefix).append("}\n");
+ if (!useRecord) {
+ writer.append("private ");
+ }
+ writer.append(actualType).append(" ").append(actualField);
+ if (!useRecord) {
+ writer.append(";\n");
+ writer.append(prefix).append("public ").append(actualType).append(" get").append(methodName).append("() {\n");
+ writer.append(prefix).append(" return ").append(actualField).append(";\n");
+ writer.append(prefix).append("}\n");
+ writer.append(prefix).append("public void set").append(methodName).append("(final ").append(actualType).append(" newValue) {\n");
+ writer.append(prefix).append(" this.").append(actualField).append(" = newValue;\n");
+ writer.append(prefix).append("}\n");
+ } else if (!last) {
+ writer.append(",");
+ }
}
private String capitalize(final String str) {
@@ -296,25 +331,16 @@
final File outputFile = new File(target, jsonToClass.replace('.', '/') + ".java");
outputFile.getParentFile().mkdirs();
- FileWriter writer = null;
- try {
- writer = new FileWriter(outputFile);
+ try (final FileWriter writer = new FileWriter(outputFile)) {
generate(readerFactory, source, writer, javaName);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
- } finally {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (final IOException e) {
- // no-op
- }
}
+ // no-op
}
private String buildValidFieldName(final String jsonField) {
- String val = jsonField;
+ String val = toJavaFieldName(jsonField);
if (Character.isDigit(jsonField.charAt(0))) {
val = "_" + jsonField;
}
diff --git a/johnzon-maven-plugin/src/test/java/org/apache/johnzon/maven/plugin/ExampleToModelMojoTest.java b/johnzon-maven-plugin/src/test/java/org/apache/johnzon/maven/plugin/ExampleToModelMojoTest.java
index d0e89e1..5a32199 100644
--- a/johnzon-maven-plugin/src/test/java/org/apache/johnzon/maven/plugin/ExampleToModelMojoTest.java
+++ b/johnzon-maven-plugin/src/test/java/org/apache/johnzon/maven/plugin/ExampleToModelMojoTest.java
@@ -114,4 +114,89 @@
new String(IOUtil.toByteArray(Thread.currentThread().getContextClassLoader().getResourceAsStream("SomeValue.java"))),
new String(IOUtil.toByteArray(new FileReader(output))).replace(File.separatorChar, '/'));
}
+
+ @Test
+ public void generateJsonbRecord() throws MojoFailureException, MojoExecutionException, IOException {
+ final File sourceFolder = new File("target/ExampleToModelMojoTest/generateJsonbRecord_source/");
+ final File targetFolder = new File("target/ExampleToModelMojoTest/generateJsonbRecord_target/");
+ final ExampleToModelMojo mojo = new ExampleToModelMojo() {{
+ source = sourceFolder;
+ target = targetFolder;
+ packageBase = "org.test.apache.johnzon.mojo";
+ useRecord = true;
+ useJsonb = true;
+ header =
+ "/*\n" +
+ " * Licensed to the Apache Software Foundation (ASF) under one\n" +
+ " * or more contributor license agreements. See the NOTICE file\n" +
+ " * distributed with this work for additional information\n" +
+ " * regarding copyright ownership. The ASF licenses this file\n" +
+ " * to you under the Apache License, Version 2.0 (the\n" +
+ " * \"License\"); you may not use this file except in compliance\n" +
+ " * with the License. You may obtain a copy of the License at\n" +
+ " *\n" +
+ " * http://www.apache.org/licenses/LICENSE-2.0\n" +
+ " *\n" +
+ " * Unless required by applicable law or agreed to in writing,\n" +
+ " * software distributed under the License is distributed on an\n" +
+ " * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" +
+ " * KIND, either express or implied. See the License for the\n" +
+ " * specific language governing permissions and limitations\n" +
+ " * under the License.\n" +
+ " */";
+ }};
+
+ sourceFolder.mkdirs();
+ final FileWriter writer = new FileWriter(new File(sourceFolder, "some-value.json"));
+ writer.write( // using openjmh as sample data
+ " {\n" +
+ " \"benchmark\" : \"com.sample.Perf.method\",\n" +
+ " \"mode\" : \"sample\",\n" +
+ " \"threads\" : 32,\n" +
+ " \"forks\" : 1,\n" +
+ " \"warmupIterations\" : 2,\n" +
+ " \"warmupTime\" : \"1 s\",\n" +
+ " \"measurementIterations\" : 3,\n" +
+ " \"measurementTime\" : \"1 s\",\n" +
+ " \"primaryMetric\" : {\n" +
+ " \"score\" : 6.951927808,\n" +
+ " \"scoreError\" : 0.7251433665600178,\n" +
+ " \"scoreConfidence\" : [\n" +
+ " 6.226784441439982,\n" +
+ " 7.677071174560018\n" +
+ " ],\n" +
+ " \"scorePercentiles\" : {\n" +
+ " \"0.0\" : 3.9468400640000003,\n" +
+ " \"50.0\" : 6.593445888000001,\n" +
+ " \"90.0\" : 9.925400985600001,\n" +
+ " \"95.0\" : 11.301132697600002,\n" +
+ " \"99.0\" : 11.844714496,\n" +
+ " \"99.9\" : 11.844714496,\n" +
+ " \"99.99\" : 11.844714496,\n" +
+ " \"99.999\" : 11.844714496,\n" +
+ " \"99.9999\" : 11.844714496,\n" +
+ " \"100.0\" : 11.844714496\n" +
+ " },\n" +
+ " \"scoreUnit\" : \"s/op\",\n" +
+ " \"rawData\" : [\n" +
+ " [\n" +
+ " 6.687817728,\n" +
+ " 7.169245184,\n" +
+ " 6.998720512\n" +
+ " ]\n" +
+ " ]\n" +
+ " },\n" +
+ " \"secondaryMetrics\" : {\n" +
+ " }\n" +
+ " }\n");
+ writer.close();
+
+ mojo.execute();
+
+ final File output = new File(targetFolder, "org/test/apache/johnzon/mojo/SomeValue.java");
+ assertTrue(output.isFile());
+ assertEquals(
+ new String(IOUtil.toByteArray(Thread.currentThread().getContextClassLoader().getResourceAsStream("SomeValue.record.java"))),
+ new String(IOUtil.toByteArray(new FileReader(output))).replace(File.separatorChar, '/'));
+ }
}
diff --git a/johnzon-maven-plugin/src/test/resources/SomeValue.record.java b/johnzon-maven-plugin/src/test/resources/SomeValue.record.java
new file mode 100644
index 0000000..120d2bd
--- /dev/null
+++ b/johnzon-maven-plugin/src/test/resources/SomeValue.record.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.test.apache.johnzon.mojo;
+
+import java.util.List;
+import javax.json.bind.annotation.JsonbProperty;
+
+public record SomeValue(
+ String benchmark,
+ String mode,
+ Double threads,
+ Double forks,
+ Double warmupIterations,
+ String warmupTime,
+ Double measurementIterations,
+ String measurementTime,
+ PrimaryMetric primaryMetric,
+ SecondaryMetrics secondaryMetrics) {
+
+ public static record PrimaryMetric(
+ Double score,
+ Double scoreError,
+ List<Double> scoreConfidence,
+ ScorePercentiles scorePercentiles,
+ String scoreUnit,
+ List<List<Double>> rawData) {
+
+ public static record ScorePercentiles(
+ @JsonbProperty("0.0") Double _00,
+ @JsonbProperty("50.0") Double _500,
+ @JsonbProperty("90.0") Double _900,
+ @JsonbProperty("95.0") Double _950,
+ @JsonbProperty("99.0") Double _990,
+ @JsonbProperty("99.9") Double _999,
+ @JsonbProperty("99.99") Double _9999,
+ @JsonbProperty("99.999") Double _99999,
+ @JsonbProperty("99.9999") Double _999999,
+ @JsonbProperty("100.0") Double _1000) {
+ }
+ }
+
+ public static record SecondaryMetrics(
+ }
+}