org.apache.johnzon.maven.plugin.ExampleToModelMojo Maven / Gradle / Ivy
/*
* 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.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 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;
@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 FORBIDDEN_JAVA_NAMES = asList('-', '_', '.');
@Parameter(property = "johnzon.source", defaultValue = "${project.basedir}/src/main/johnzon")
protected File source;
@Parameter(property = "johnzon.target", defaultValue = "${project.build.directory}/generated-sources/johnzon")
protected File target;
@Parameter(property = "johnzon.package", defaultValue = "com.johnzon.generated")
protected String packageBase;
@Parameter
protected String header;
@Parameter
protected MavenProject project;
@Parameter(property = "johnzon.attach", defaultValue = "true")
protected boolean attach;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
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");
}
});
if (children == null || children.length == 0) {
throw new MojoExecutionException("No json file found in " + source);
}
for (final File child : children) {
generateFile(readerFactory, child);
}
}
if (attach && project != null) {
project.addCompileSourceRoot(target.getAbsolutePath());
}
}
// 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));
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 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();
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");
} 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,
final Collection imports) throws IOException {
final Map nestedTypes = new TreeMap();
{
final Iterator> iterator = object.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry entry = iterator.next();
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);
break;
case OBJECT:
final String type = toJavaName(fieldName);
nestedTypes.put(type, JsonObject.class.cast(entry.getValue()));
fieldGetSetMethods(writer, key, fieldName, type, prefix, 0, imports);
break;
case TRUE:
case FALSE:
fieldGetSetMethods(writer, key, fieldName, "Boolean", prefix, 0, imports);
break;
case NUMBER:
fieldGetSetMethods(writer, key, fieldName, "Double", prefix, 0, imports);
break;
case STRING:
fieldGetSetMethods(writer, key, fieldName, "String", prefix, 0, imports);
break;
case NULL:
default:
throw new UnsupportedOperationException("Unsupported " + entry.getValue() + ".");
}
if (iterator.hasNext()) {
writer.write("\n");
}
}
}
if (!object.isEmpty() && !nestedTypes.isEmpty()) {
writer.write("\n");
}
final Iterator> entries = nestedTypes.entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry 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");
}
}
}
private void handleArray(final Writer writer, final String prefix,
final Map nestedTypes,
final JsonValue value,
final String jsonField,final String fieldName,
final int arrayLevel,
final Collection imports) 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);
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);
break;
case TRUE:
case FALSE:
fieldGetSetMethods(writer, jsonField, fieldName, "Boolean", prefix, arrayLevel, imports);
break;
case NUMBER:
fieldGetSetMethods(writer, jsonField, fieldName, "Double", prefix, arrayLevel, imports);
break;
case STRING:
fieldGetSetMethods(writer, jsonField, fieldName, "String", prefix, arrayLevel, imports);
break;
case ARRAY:
handleArray(writer, prefix, nestedTypes, jsonValue, jsonField, fieldName, arrayLevel + 1, imports);
break;
case NULL:
default:
throw new UnsupportedOperationException("Unsupported " + value + ".");
}
} else {
getLog().warn("Empty arrays are ignored");
}
}
private void fieldGetSetMethods(final Writer writer,
final String jsonField, final String field,
final String type, final String prefix, final int arrayLevel,
final Collection imports) throws IOException {
final String 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");
}
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 buildArrayType(final int arrayLevel, final String 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();
}
private void visit(final JsonStructure structure, final Visitor visitor) {
if (JsonObject.class.isInstance(structure)) {
visitor.visitObject(JsonObject.class.cast(structure));
} else if (JsonArray.class.isInstance(structure)) {
visitor.visitArray(JsonArray.class.cast(structure));
} else {
throw new UnsupportedOperationException("Can't visit " + structure);
}
}
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
}
}
}
private String buildValidFieldName(final String jsonField) {
String val = jsonField;
if (Character.isDigit(jsonField.charAt(0))) {
val = "_" + jsonField;
}
return val.replace(".", "");
}
private String toJavaFieldName(final String key) {
return StringUtils.uncapitalize(toJavaName(key));
}
private String toJavaName(final String file) {
final StringBuilder builder = new StringBuilder();
boolean nextUpper = false;
for (final char anIn : file.replace(".json", "").toCharArray()) {
if (FORBIDDEN_JAVA_NAMES.contains(anIn)) {
nextUpper = true;
} else if (nextUpper) {
builder.append(Character.toUpperCase(anIn));
nextUpper = false;
} else {
builder.append(anIn);
}
}
return StringUtils.capitalize(builder.toString());
}
private interface Visitor {
void visitObject(JsonObject structure);
void visitArray(JsonArray structure);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy