This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push: new 56a6c1b [JOHNZON-350] record support in example to model mojo 56a6c1b is described below commit 56a6c1b0d918bce494b31c8cd7689149ba73495c Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Tue Jun 29 10:35:06 2021 +0200 [JOHNZON-350] record support in example to model mojo --- .../johnzon/maven/plugin/ExampleToModelMojo.java | 148 ++++++++++++--------- .../maven/plugin/ExampleToModelMojoTest.java | 85 ++++++++++++ .../src/test/resources/SomeValue.record.java | 60 +++++++++ 3 files changed, 232 insertions(+), 61 deletions(-) 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 @@ package org.apache.johnzon.maven.plugin; 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 javax.json.JsonValue; 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 @@ import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURC @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 @@ public class ExampleToModelMojo extends AbstractMojo { @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 @@ public class ExampleToModelMojo extends AbstractMojo { // 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 @@ public class ExampleToModelMojo extends AbstractMojo { 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 @@ public class ExampleToModelMojo extends AbstractMojo { 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 @@ public class ExampleToModelMojo extends AbstractMojo { 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 @@ public class ExampleToModelMojo extends AbstractMojo { 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 @@ public class ExampleToModelMojo extends AbstractMojo { 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); + } + 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(","); } - - 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"); } private String capitalize(final String str) { @@ -296,25 +331,16 @@ public class ExampleToModelMojo extends AbstractMojo { 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 @@ public class ExampleToModelMojoTest { 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( + } +}