/* Copyright 2014 Danish Maritime Authority. * * 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 net.maritimecloud.common.cqrs.contract; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; /** * Converts methods of an interface-file into corresponding (serializable) Value Objects for use as Commands and Events. * <p> * @author Christoffer Børrild */ public class SourceGenerator { private static final String EOL = "\n"; private final Class contractClass; private String baseTarget = "./target/generated-sources/cqrs/"; private boolean jsonizeCommands = true; private boolean writeSetters = false; public SourceGenerator(Class contractClass) { this.contractClass = contractClass; } public SourceGenerator(Class contractClass, String baseTarget) { this.contractClass = contractClass; this.baseTarget = baseTarget; } public void generate() throws IOException { System.out.println("generating " + contractClass); generateClasses(); } private void generateClasses() throws IOException { Method[] declaredMethods = getClassMethods(); System.out.println("declaredMethods " + declaredMethods.length); for (Method declaredMethod : declaredMethods) { generateClass(declaredMethod); } } private Method[] getClassMethods() { Method[] declaredMethods = contractClass.getDeclaredMethods(); return declaredMethods; } private void generateClass(Method declaredMethod) throws IOException { String packageBaseName = contractClass.getPackage().getName() + ".api"; String className = capitalizeFirstLetter(declaredMethod.getName()); File file = new File(baseTarget + pathOf(packageBaseName), className + ".java"); System.out.println("Writing command file to " + file.getCanonicalPath()); if (!file.exists()) { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } file.createNewFile(); } try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file))) { MethodToClassWriter writer = new MethodToClassWriter(osw, declaredMethod); writer.writeHeader(); writer.writePackage(packageBaseName); writer.writeImports(); writer.writeClass(); osw.flush(); } System.out.println("Done writing command"); } private String pathOf(String packageBaseName) { return packageBaseName.replaceAll("\\.", "/"); } private String capitalizeFirstLetter(String name) { return name.substring(0, 1).toUpperCase().concat(name.substring(1)); } private class MethodToClassWriter { private final OutputStreamWriter osw; private final Method declaredMethod; private final String className; private final boolean isCommand; public MethodToClassWriter(OutputStreamWriter osw, Method declaredMethod) { this.osw = osw; this.declaredMethod = declaredMethod; this.className = capitalizeFirstLetter(declaredMethod.getName()); this.isCommand = methodIsACommand(); } private boolean methodIsACommand() { return className.endsWith("Command") || declaredMethod.getAnnotation(Command.class) != null; } public void writeHeader() throws IOException { osw.write("// This code was generated by " + SourceGenerator.class.getCanonicalName() + EOL); osw.write("// Generated Code is based on the contract defined in " + contractClass.getCanonicalName() + EOL); osw.write("// Please modify the contract instead of this file!" + EOL); } public void writePackage(String packageBaseName) throws IOException { osw.write("package " + packageBaseName + ";" + EOL); osw.write(EOL); } private void writeImports() throws IOException { writeImport(org.axonframework.commandhandling.annotation.TargetAggregateIdentifier.class); if (isCommand) { writeImport(org.axonframework.common.Assert.class); writeImport(com.fasterxml.jackson.annotation.JsonCreator.class); writeImport(com.fasterxml.jackson.annotation.JsonProperty.class); writeImport(net.maritimecloud.common.cqrs.Command.class); } else { writeImport(Event.class); } for (Parameter parameter : declaredMethod.getParameters()) { writeImport(parameter); } osw.write(EOL); } private void writeImport(Class aClass) throws IOException { osw.write("import " + aClass.getCanonicalName() + ";" + EOL); } private void writeImport(Parameter parameter) throws IOException { // Skip java.lang-types if (!parameter.getType().getName().startsWith("java.lang.")) { osw.write("import " + parameter.getType().getName() + ";" + EOL); } } private void writeClass() throws IOException { osw.write("/**" + EOL); osw.write(" * GENERATED CLASS!" + EOL); osw.write(" * @see " + contractClass.getName() + "#" + declaredMethod.getName() + EOL); osw.write(" */" + EOL); if (isEvent()) { osw.write("@Event" + EOL); } osw.write("public class " + className + (isCommand ? " implements Command" : getExtendsSentenceIfAny()) + " {" + EOL); osw.write(EOL); writeProperties(); if (isEvent() && writeSetters) { writeDefaultConstructor(); } writeConstructor(); writeAccessors(); osw.write("}" + EOL); osw.write(EOL); } private String getExtendsSentenceIfAny() { return hasSuper() ? " extends " + declaredMethod.getAnnotation(Event.class).extend()[0] : ""; } private boolean hasSuper() { return declaredMethod.getAnnotation(Event.class) != null && declaredMethod.getAnnotation(Event.class).extend().length > 0; } private void writeProperties() throws IOException { writeTargetAggregateIdentifierAnnotationIfTrue(!hasTargetAggregateIdentifierAnnotation()); for (Parameter parameter : declaredMethod.getParameters()) { writeTargetAggregateIdentifierAnnotationIfTrue(hasTargetAggregateIdentifierAnnotation(parameter)); writeProperty(parameter); } } private boolean hasTargetAggregateIdentifierAnnotation() { for (Parameter parameter : declaredMethod.getParameters()) { if (hasTargetAggregateIdentifierAnnotation(parameter)) { return true; } } return false; } private boolean hasTargetAggregateIdentifierAnnotation(Parameter parameter) { return parameter.getAnnotation(TargetAggregateIdentifier.class) != null; } private void writeTargetAggregateIdentifierAnnotationIfTrue(boolean shouldWrite) throws IOException { if (shouldWrite) { osw.write(" @TargetAggregateIdentifier" + EOL); } } private void writeProperty(Parameter parameter) throws IOException { String modifier = isCommand || !writeSetters ? "final " : ""; osw.write(" private " + modifier + parameter.getType().getSimpleName() + " " + parameter.getName() + ";" + EOL); } private void writeDefaultConstructor() throws IOException { osw.write(" private " + className + "(){};" + EOL); } private void writeConstructor() throws IOException { osw.write(EOL); if (isCommand) { osw.write(" @JsonCreator" + EOL); } osw.write(" public " + className + "(" + EOL); writeParameters(); osw.write(" ) {" + EOL); if (isCommand) { writeAssertions(); } if (hasSuper()) { writeCallToSuper(); } writeAssignments(); osw.write(" }" + EOL); } private void writeParameters() throws IOException { writeParameters(true); } private void writeParameters(boolean includeType) throws IOException { for (int i = 0; i < declaredMethod.getParameters().length; i++) { Parameter parameter = declaredMethod.getParameters()[i]; boolean isLast = i == declaredMethod.getParameters().length - 1; writeParameter(parameter, isLast, includeType); } } private void writeParameter(Parameter parameter, boolean isLast) throws IOException { writeParameter(parameter, isLast, true); } private void writeParameter(Parameter parameter, boolean isLast, boolean includeType) throws IOException { osw.write(" "); if (includeType) { if (isCommand) { osw.write("@JsonProperty(\"" + parameter.getName() + "\") "); writeAnnotations(parameter); } osw.write(parameter.getType().getSimpleName() + " "); } osw.write(parameter.getName()); if (!isLast) { osw.write(","); } osw.write(EOL); } private void writeAnnotations(Parameter parameter) throws IOException { for (Annotation declaredAnnotation : parameter.getDeclaredAnnotations()) { writeAnnotation(declaredAnnotation); } } private void writeAnnotation(Annotation declaredAnnotation) throws IOException { // TODO: introduce an annotaion that will allow to transfer annotations as a string if (declaredAnnotation instanceof JsonSerialize) { //osw.write("@JsonSerialize(using=" + ((JsonSerialize) declaredAnnotation).contentUsing().getName() + ".class " + EOL); } } private void writeAssertions() throws IOException { for (Parameter parameter : declaredMethod.getParameters()) { writeAssertion(parameter); } } private void writeAssertion(Parameter parameter) throws IOException { osw.write(" Assert.notNull(" + parameter.getName() + ", \"The " + parameter.getName() + " must be provided\");" + EOL); } private void writeCallToSuper() throws IOException { osw.write(" super(" + EOL); writeParameters(false); osw.write(" );" + EOL); } private void writeAssignments() throws IOException { for (Parameter parameter : declaredMethod.getParameters()) { writeAssignment(parameter); } } private void writeAssignment(Parameter parameter) throws IOException { osw.write(" this." + parameter.getName() + " = " + parameter.getName() + ";" + EOL); } private void writeAccessors() throws IOException { for (Parameter parameter : declaredMethod.getParameters()) { writeGetter(parameter); if (isEvent() && writeSetters) { writeSetter(parameter); } } osw.write("" + EOL); } private void writeGetter(Parameter parameter) throws IOException { osw.write(EOL); osw.write(" public " + parameter.getType().getSimpleName() + " get" + capitalizeFirstLetter(parameter.getName()) + "() {" + EOL); osw.write(" return " + parameter.getName() + ";" + EOL); osw.write(" }" + EOL); } private void writeSetter(Parameter parameter) throws IOException { osw.write(EOL); osw.write(" public void set" + capitalizeFirstLetter(parameter.getName()) + "("); writeParameter(parameter, true); osw.write(") {" + EOL); writeAssignment(parameter); osw.write(" }" + EOL); } private boolean isEvent() { return !isCommand; } } }