/* 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;
}
}
}