Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
13 changes: 13 additions & 0 deletions johnzon-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
<version>3.4</version>
</dependency>

<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.11.1</version>
</dependency>

<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
Expand All @@ -69,6 +75,13 @@
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
</dependency>

<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.6.24</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,44 @@
*/
package org.apache.johnzon.maven.plugin;

import org.apache.commons.lang3.StringUtils;
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;
import static java.util.Arrays.asList;
import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonReaderFactory;
import javax.json.JsonStructure;
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;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import static java.util.Arrays.asList;
import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonReaderFactory;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import javax.lang.model.element.Modifier;

import org.apache.commons.lang3.StringUtils;
import org.apache.johnzon.mapper.JohnzonProperty;
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;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

@Mojo(name = "example-to-model", defaultPhase = GENERATE_SOURCES)
public class ExampleToModelMojo extends AbstractMojo {
Expand Down Expand Up @@ -98,8 +104,24 @@ public boolean accept(final File dir, final String name) {
}
}

private void generate(final JsonObject object, final TypeSpec.Builder targetType) throws MojoExecutionException {
targetType.addModifiers(Modifier.PUBLIC);

if (header != null) {
targetType.addJavadoc(header);
}

generateFieldsAndMethods(object, targetType);

try {
JavaFile.builder(packageBase, targetType.build()).build().writeTo(target);
} catch (IOException e) {
throw new MojoExecutionException("An error occurred while serializing class " + targetType, e);
}
}

// 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 {
private void generate(final JsonReaderFactory readerFactory, final File source, final TypeSpec.Builder targetType) throws MojoExecutionException {
JsonReader reader = null;
try {
reader = readerFactory.createReader(new FileReader(source));
Expand All @@ -109,29 +131,8 @@ private void generate(final JsonReaderFactory readerFactory, final File source,
}

final JsonObject object = JsonObject.class.cast(structure);
final Collection<String> imports = new TreeSet<String>();

// while we browse the example tree just store imports as well, avoids a 2 passes processing duplicating imports logic
final StringWriter memBuffer = new StringWriter();
generateFieldsAndMethods(memBuffer, object, " ", imports);

if (header != null) {
writer.write(header);
writer.write('\n');
}

writer.write("package " + packageBase + ";\n\n");

if (!imports.isEmpty()) {
for (final String imp : imports) {
writer.write("import " + imp + ";\n");
}
writer.write('\n');
}

writer.write("public class " + javaName + " {\n");
writer.write(memBuffer.toString());
writer.write("}\n");
generate(object, targetType);
} catch (final IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
} finally {
Expand All @@ -141,88 +142,74 @@ private void generate(final JsonReaderFactory readerFactory, final File source,
}
}

private void generateFieldsAndMethods(final Writer writer, final JsonObject object, final String prefix,
final Collection<String> imports) throws IOException {
private void generateFieldsAndMethods(final JsonObject object, TypeSpec.Builder targetType) throws MojoExecutionException {
final Map<String, JsonObject> nestedTypes = new TreeMap<String, JsonObject>();
{
final Iterator<Map.Entry<String, JsonValue>> iterator = object.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, JsonValue> entry = iterator.next();
for (final Map.Entry<String, JsonValue> entry : object.entrySet()) {
final String key = entry.getKey();
final String fieldName = toJavaFieldName(key);
switch (entry.getValue().getValueType()) {
case ARRAY:
imports.add("java.util.List");
handleArray(writer, prefix, nestedTypes, entry.getValue(), key, fieldName, 1, imports);
handleArray(targetType, nestedTypes, entry.getValue(), key, fieldName, 1);
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(targetType, key, fieldName, ClassName.get(packageBase, type), 0);
break;
case TRUE:
case FALSE:
fieldGetSetMethods(writer, key, fieldName, "Boolean", prefix, 0, imports);
fieldGetSetMethods(targetType, key, fieldName, ClassName.get(Boolean.class), 0);
break;
case NUMBER:
fieldGetSetMethods(writer, key, fieldName, "Double", prefix, 0, imports);
fieldGetSetMethods(targetType, key, fieldName, ClassName.get(Double.class), 0);
break;
case STRING:
fieldGetSetMethods(writer, key, fieldName, "String", prefix, 0, imports);
fieldGetSetMethods(targetType, key, fieldName, ClassName.get(String.class), 0);
break;
case NULL:
default:
throw new UnsupportedOperationException("Unsupported " + entry.getValue() + ".");
}
if (iterator.hasNext()) {
writer.write("\n");
}
}
}

if (!object.isEmpty() && !nestedTypes.isEmpty()) {
writer.write("\n");
}
for (final Map.Entry<String, JsonObject> entry : nestedTypes.entrySet()) {
ClassName nestedType = ClassName.get(packageBase, entry.getKey());
TypeSpec.Builder nestedTypeSpec = TypeSpec.classBuilder(nestedType);

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");
generateFieldsAndMethods(writer, entry.getValue(), " " + prefix, imports);
writer.write(prefix + "}\n");
if (entries.hasNext()) {
writer.write("\n");
}
generate(entry.getValue(), nestedTypeSpec);
}
}

private void handleArray(final Writer writer, final String prefix,
private void handleArray(final TypeSpec.Builder targetType,
final Map<String, JsonObject> nestedTypes,
final JsonValue value,
final String jsonField,final String fieldName,
final int arrayLevel,
final Collection<String> imports) throws IOException {
final String jsonField,
final String fieldName,
final int arrayLevel) {
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);
switch (jsonValue.getValueType()) {
case OBJECT:
final String javaName = toJavaName(fieldName);
nestedTypes.put(javaName, JsonObject.class.cast(jsonValue));
fieldGetSetMethods(writer, jsonField, fieldName, javaName, prefix, arrayLevel, imports);
final ClassName javaType = ClassName.bestGuess(javaName);
fieldGetSetMethods(targetType, jsonField, fieldName, javaType, arrayLevel);
break;
case TRUE:
case FALSE:
fieldGetSetMethods(writer, jsonField, fieldName, "Boolean", prefix, arrayLevel, imports);
fieldGetSetMethods(targetType, jsonField, fieldName, ClassName.get(Boolean.class), arrayLevel);
break;
case NUMBER:
fieldGetSetMethods(writer, jsonField, fieldName, "Double", prefix, arrayLevel, imports);
fieldGetSetMethods(targetType, jsonField, fieldName, ClassName.get(Double.class), arrayLevel);
break;
case STRING:
fieldGetSetMethods(writer, jsonField, fieldName, "String", prefix, arrayLevel, imports);
fieldGetSetMethods(targetType, jsonField, fieldName, ClassName.get(String.class), arrayLevel);
break;
case ARRAY:
handleArray(writer, prefix, nestedTypes, jsonValue, jsonField, fieldName, arrayLevel + 1, imports);
handleArray(targetType, nestedTypes, jsonValue, jsonField, fieldName, arrayLevel + 1);
break;
case NULL:
default:
Expand All @@ -233,42 +220,44 @@ private void handleArray(final Writer writer, final String prefix,
}
}

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 String actualType = buildArrayType(arrayLevel, type);
private void fieldGetSetMethods(final TypeSpec.Builder targetType,
final String jsonField,
final String field,
final ClassName type,
final int arrayLevel) {
final TypeName actualType = buildArrayType(arrayLevel, type);
final String actualField = buildValidFieldName(jsonField);
final String methodName = StringUtils.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");
FieldSpec.Builder fieldBuilder = FieldSpec.builder(actualType, actualField, Modifier.PRIVATE);

if (!jsonField.equals(field)) {
fieldBuilder.addAnnotation(AnnotationSpec.builder(JohnzonProperty.class)
.addMember("value", "$S", jsonField)
.build());
}

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");
targetType.addField(fieldBuilder.build());

targetType.addMethod(MethodSpec.methodBuilder("get" + methodName)
.addModifiers(Modifier.PUBLIC)
.returns(actualType)
.addStatement("return $L", actualField)
.build());

targetType.addMethod(MethodSpec.methodBuilder("set" + methodName)
.addModifiers(Modifier.PUBLIC)
.addParameter(actualType, actualField, Modifier.FINAL)
.addStatement("this.$1L = $1L", actualField)
.build());
}

private String buildArrayType(final int arrayLevel, final String type) {
private TypeName buildArrayType(final int arrayLevel, final ClassName type) {
if (arrayLevel == 0) { // quick exit
return type;
}

final StringBuilder builder = new StringBuilder();
for (int i = 0; i < arrayLevel; i++) {
builder.append("List<");
}
builder.append(type);
for (int i = 0; i < arrayLevel; i++) {
builder.append(">");
}
return builder.toString();
return ParameterizedTypeName.get(ClassName.get(List.class), type);
}

private void visit(final JsonStructure structure, final Visitor visitor) {
Expand All @@ -283,25 +272,10 @@ private void visit(final JsonStructure structure, final Visitor visitor) {

private void generateFile(final JsonReaderFactory readerFactory, final File source) throws MojoExecutionException {
final String javaName = StringUtils.capitalize(toJavaName(source.getName()));
final String jsonToClass = packageBase + '.' + javaName;
final File outputFile = new File(target, jsonToClass.replace('.', '/') + ".java");

outputFile.getParentFile().mkdirs();
FileWriter writer = null;
try {
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
}
}
TypeSpec.Builder targetType = TypeSpec.classBuilder(javaName).addModifiers(Modifier.PUBLIC);

generate(readerFactory, source, targetType);
}

private String buildValidFieldName(final String jsonField) {
Expand Down
Loading