package joist.codegen.passes; import java.util.ArrayList; import java.util.List; import joist.codegen.Codegen; import joist.codegen.dtos.CodeEntity; import joist.codegen.dtos.CodeValue; import joist.codegen.dtos.Entity; import joist.codegen.dtos.ManyToManyProperty; import joist.codegen.dtos.ManyToOneProperty; import joist.codegen.dtos.OneToManyProperty; import joist.codegen.dtos.PrimitiveProperty; import joist.domain.builders.AbstractBuilder; import joist.domain.builders.DefaultsContext; import joist.domain.uow.UoW; import joist.sourcegen.Argument; import joist.sourcegen.GClass; import joist.sourcegen.GMethod; import joist.util.Inflector; import org.apache.commons.lang.StringUtils; public class GenerateBuilderCodegenPass implements Pass<Codegen> { public void pass(Codegen codegen) { for (Entity entity : codegen.getSchema().getEntities().values()) { if (entity.isCodeEntity()) { continue; } GClass builderCodegen = codegen.getOutputCodegenDirectory().getClass(entity.getFullBuilderCodegenClassName()); builderCodegen.setAbstract(); if (entity.isRoot()) { builderCodegen.baseClassName("AbstractBuilder<{}>", entity.getFullClassName()); builderCodegen.addImports(AbstractBuilder.class); } else { builderCodegen.baseClassName(entity.getParentClassName() + "Builder"); } builderCodegen.addAnnotation("@SuppressWarnings(\"all\")"); // this defaults() is just for the cast GMethod defaults2 = builderCodegen.getMethod("defaults"); defaults2.addAnnotation("@Override"); defaults2.body.line("return ({}) super.defaults();", entity.getBuilderClassName()); defaults2.returnType(entity.getBuilderClassName()); // this defaults is to actually fill in GMethod defaults = builderCodegen.getMethod("defaults", Argument.arg(DefaultsContext.class, "c")); defaults.addAnnotation("@Override").setProtected(); defaults.body.line("super.defaults(c);"); this.constructor(builderCodegen, entity); this.primitiveProperties(codegen, builderCodegen, entity, defaults); this.manyToOneProperties(builderCodegen, entity, defaults); this.oneToManyProperties(builderCodegen, entity); this.manyToManyProperties(builderCodegen, entity); this.overrideGet(builderCodegen, entity); this.ensureSaved(builderCodegen, entity); this.use(builderCodegen, entity); this.delete(builderCodegen, entity); } } private void constructor(GClass builderCodegen, Entity entity) { GMethod m = builderCodegen.getConstructor(entity.getFullClassName() + " instance"); m.body.line("super(instance);"); } private void overrideGet(GClass builderCodegen, Entity entity) { builderCodegen.getMethod("get").returnType(entity.getFullClassName()).body.line("return ({}) super.get();", entity.getFullClassName()); } private void ensureSaved(GClass builderCodegen, Entity entity) { GMethod m = builderCodegen.getMethod("ensureSaved").returnType(entity.getBuilderClassName()).addAnnotation("@Override"); m.body.line("doEnsureSaved();"); m.body.line("return ({}) this;", entity.getBuilderClassName()); builderCodegen.addImports(UoW.class); } private void use(GClass builderCodegen, Entity entity) { GMethod m = builderCodegen.getMethod("use", Argument.arg("AbstractBuilder<?>", "builder")); m.returnType(entity.getBuilderClassName()).addAnnotation("@Override"); m.body.line("return ({}) super.use(builder);", entity.getBuilderClassName()); builderCodegen.addImports(AbstractBuilder.class); } private void delete(GClass builderCodegen, Entity entity) { if (entity.isSubclass()) { return; } GMethod delete = builderCodegen.getMethod("delete").addAnnotation("@Override"); delete.body.line("{}.queries.delete(get());", entity.getClassName()); GMethod deleteAll = builderCodegen.getMethod("deleteAll").setStatic(); deleteAll.body.line("List<Long> ids = {}.queries.findAllIds();", entity.getClassName()); deleteAll.body.line("for (Long id : ids) {"); deleteAll.body.line(" {}.queries.delete({}.queries.find(id));", entity.getClassName(), entity.getClassName()); deleteAll.body.line("}"); builderCodegen.addImports(List.class); } private void primitiveProperties(Codegen codegen, GClass c, Entity entity, GMethod defaults) { for (PrimitiveProperty p : entity.getPrimitiveProperties()) { if (p.getVariableName().equals("version")) { continue; } if (p.getVariableName().equals("id")) { GMethod m = c.getMethod("id").returnType(p.getJavaType()); m.body.line("if (UoW.isOpen() && get().getId() == null) {"); m.body.line("_ UoW.flush();"); m.body.line("}"); // let addFluentGetter call below finish the method (add the return) c.addImports(UoW.class); } // regular foo() getter this.addFluentGetter(c, p.getVariableName(), p.getJavaType()); // regular foo(value) setter this.addFluentSetter(c, entity, p.getVariableName(), p.getJavaType()); // overload with(value) setter if (entity.getUniquePropertyTypes().contains(p.getJavaType())) { this.addFluentWith(c, entity, p.getVariableName(), p.getJavaType()); } // add to defaults if (p.shouldHaveNotNullRule()) { String defaultValue; if (String.class.getName().equals(p.getJavaType())) { defaultValue = "\"" + p.getVariableName() + "\""; } else { defaultValue = codegen.getConfig().getBuildersDefault(p.getJavaType()); } // user types may not have configured defaults if (defaultValue != null) { this.addToDefaults(c, defaults, p.getVariableName(), p.getJavaType(), defaultValue); } } } // add covariant return types for (Entity base : entity.getBaseEntities()) { for (PrimitiveProperty p : base.getPrimitiveProperties()) { if (p.getVariableName().equals("version") || p.getVariableName().equals("id")) { continue; } this.addFluentGetter(c, p.getVariableName(), p.getJavaType()); // for scalac bug this.addFluentSetter(c, entity, p.getVariableName(), p.getJavaType()); } } } private void oneToManyProperties(GClass c, Entity entity) { for (OneToManyProperty otom : entity.getOneToManyProperties()) { if (otom.isManyToMany()) { continue; } // newChild() -> ChildBuilder if (!otom.getManySide().isAbstract()) { GMethod m = c.getMethod("new" + otom.getCapitalVariableNameSingular()); m.returnType("{}Builder", otom.getTargetJavaType()); m.body.line("return Builders.a{}().{}(({}) this);", // otom.getTargetJavaType(), StringUtils.uncapitalize(otom.getManyToOneProperty().getCapitalVariableName()), entity.getBuilderClassName()); } if (otom.isCollectionSkipped()) { // add a special childs() -> List<ChildBuilder> that uses the find ids query { GMethod m = c.getMethod(otom.getVariableName()); m.returnType("List<{}Builder>", otom.getTargetJavaType()); m.body.line("UoW.flush();"); m.body.line("List<{}Builder> b = new ArrayList<{}Builder>();", otom.getTargetJavaType(), otom.getTargetJavaType()); // otom.getTargetJavaType(), m.body.line("for (Long id : {}.queries.find{}Ids(get())) {", entity.getClassName(), otom.getCapitalVariableName()); m.body.line("_ b.add(Builders.the{}(id));", otom.getManySide().getClassName()); m.body.line("}"); m.body.line("return b;"); c.addImports(UoW.class, List.class, ArrayList.class); c.addImports(otom.getManySide().getFullClassName()); } // child(i) -> ChildBuilder { GMethod m = c.getMethod(StringUtils.uncapitalize(otom.getCapitalVariableNameSingular()), Argument.arg("int", "i")); m.returnType("{}Builder", otom.getTargetJavaType()); m.body.line("return {}().get(i);", otom.getVariableName()); } } else if (otom.isOneToOne()) { // child() -> ChildBuilder GMethod m = c.getMethod(otom.getVariableNameSingular()); m.returnType("{}Builder", otom.getTargetJavaType()); m.body.line("if (get().get{}() == null) {", otom.getCapitalVariableNameSingular()); m.body.line("_ return null;"); m.body.line("}"); m.body.line("return Builders.existing(get().get{}());", otom.getCapitalVariableNameSingular()); } else { // childs() -> List<ChildBuilder> { GMethod m = c.getMethod(otom.getVariableName()); m.returnType("List<{}Builder>", otom.getTargetJavaType()); m.body.line("List<{}Builder> b = new ArrayList<{}Builder>();", otom.getTargetJavaType(), otom.getTargetJavaType()); m.body.line("for ({} e : get().get{}()) {", otom.getTargetJavaType(), otom.getCapitalVariableName()); m.body.line("_ b.add(Builders.existing(e));"); m.body.line("}"); m.body.line("return b;"); c.addImports(List.class, ArrayList.class); c.addImports(otom.getManySide().getFullClassName()); } // child(i) -> ChildBuilder { GMethod m = c.getMethod(StringUtils.uncapitalize(otom.getCapitalVariableNameSingular()), Argument.arg("int", "i")); m.returnType("{}Builder", otom.getTargetJavaType()); m.body.line("return Builders.existing(get().get{}().get(i));", otom.getCapitalVariableName()); } } } } private void manyToOneProperties(GClass c, Entity entity, GMethod defaults) { // add an up-front pass to get already-set properties into DefaultsContext for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { if (!mtop.getOneSide().isCodeEntity()) { defaults.body.line("c.rememberIfSet({}());", mtop.getVariableName()); } } // now add the regular getters/setters/etc. for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { // regular foo() getter if (mtop.getOneSide().isCodeEntity()) { this.addFluentGetter(c, mtop.getVariableName(), mtop.getJavaType()); } else { this.addFluentBuilderGetter(c, mtop.getVariableName(), mtop.getJavaType()); } // regular foo(value) setter this.addFluentSetter(c, entity, mtop.getVariableName(), mtop.getOneSide().getFullClassName()); // overload with(value) setter if (entity.getUniquePropertyTypes().contains(mtop.getJavaType())) { this.addFluentWith(c, entity, mtop.getVariableName(), mtop.getOneSide().getFullClassName()); } if (!mtop.getOneSide().isCodeEntity()) { // regular foo(valueBuilder) setter this.addFluentBuilderSetter(c, entity, mtop.getVariableName(), mtop.getOneSide().getBuilderClassName()); // overload with(valueBuilder) setter if (entity.getUniquePropertyTypes().contains(mtop.getJavaType())) { this.addFluentWith(c, entity, mtop.getVariableName(), mtop.getOneSide().getBuilderClassName()); } } // add to defaults if (mtop.isNotNull()) { if (mtop.getOneSide().isCodeEntity()) { CodeEntity ce = (CodeEntity) mtop.getOneSide(); String defaultValue = ce.getClassName() + "." + ce.getCodes().get(0).getEnumName(); this.addToDefaults(c, defaults, mtop.getVariableName(), mtop.getJavaType(), defaultValue); } else if (!mtop.getOneSide().isAbstract()) { String defaultValue = "Builders.a" + mtop.getOneSide().getClassName() + "().defaults()"; this.addToDefaultsWithContextLookup(c, defaults, mtop, defaultValue); } } // add codeValue() methods if (mtop.getOneSide().isCodeEntity()) { for (CodeValue code : ((CodeEntity) mtop.getOneSide()).getCodes()) { if (entity.getUniqueCodeNames().contains(code.getEnumName())) { { // e.g. blue() GMethod m = c.getMethod(Inflector.uncapitalize(code.getNameCamelCased())); m.returnType(entity.getBuilderClassName()); m.body.line("return {}({}.{});", mtop.getVariableName(), mtop.getOneSide().getClassName(), code.getEnumName()); } { // e.g. isBlue() GMethod m = c.getMethod("is" + code.getNameCamelCased()).returnType("boolean"); m.body.line("return get().is{}();", code.getNameCamelCased()); } } } } } // add covariant return types for (Entity base : entity.getBaseEntities()) { for (ManyToOneProperty mtop : base.getManyToOneProperties()) { // regular foo() getter, for scalac bug if (mtop.getOneSide().isCodeEntity()) { this.addFluentGetter(c, mtop.getVariableName(), mtop.getJavaType()); } else { this.addFluentBuilderGetter(c, mtop.getVariableName(), mtop.getJavaType()); } // regular foo(value) setter this.addFluentSetter(c, entity, mtop.getVariableName(), mtop.getOneSide().getFullClassName()); // overload with(value) setter if (entity.getUniquePropertyTypes().contains(mtop.getJavaType())) { this.addFluentWith(c, entity, mtop.getVariableName(), mtop.getOneSide().getFullClassName()); } if (!mtop.getOneSide().isCodeEntity()) { // regular foo(valueBuilder) setter this.addFluentBuilderSetter(c, entity, mtop.getVariableName(), mtop.getOneSide().getBuilderClassName()); // overload with(valueBuilder) setter if (entity.getUniquePropertyTypes().contains(mtop.getJavaType())) { this.addFluentWith(c, entity, mtop.getVariableName(), mtop.getOneSide().getBuilderClassName()); } } else { for (CodeValue code : ((CodeEntity) mtop.getOneSide()).getCodes()) { if (entity.getUniqueCodeNames().contains(code.getEnumName())) { // e.g. blue() GMethod m = c.getMethod(Inflector.uncapitalize(code.getNameCamelCased())); m.returnType(entity.getBuilderClassName()); m.body.line("return {}({}.{});", mtop.getVariableName(), mtop.getOneSide().getClassName(), code.getEnumName()); } } } } } } private void manyToManyProperties(GClass c, Entity entity) { for (ManyToManyProperty mtmp : entity.getManyToManyProperties()) { if (mtmp.getMySideOneToMany().isCollectionSkipped() || mtmp.getTargetTable().isCodeEntity()) { continue; } // childs() -> List<ChildBuilder> { GMethod m = c.getMethod(mtmp.getVariableName()); m.returnType("List<{}Builder>", mtmp.getTargetJavaType()); m.body.line("List<{}Builder> b = new ArrayList<{}Builder>();", mtmp.getTargetJavaType(), mtmp.getTargetJavaType()); m.body.line("for ({} e : get().get{}()) {", mtmp.getTargetJavaType(), mtmp.getCapitalVariableName()); m.body.line("_ b.add(Builders.existing(e));"); m.body.line("}"); m.body.line("return b;"); c.addImports(List.class, ArrayList.class); c.addImports(mtmp.getTargetTable().getFullClassName()); } // child(i) -> ChildBuilder { GMethod m = c.getMethod(StringUtils.uncapitalize(mtmp.getCapitalVariableNameSingular()), Argument.arg("int", "i")); m.returnType("{}Builder", mtmp.getTargetJavaType()); m.body.line("return Builders.existing(get().get{}().get(i));", mtmp.getCapitalVariableName()); } this.addFluentSetter(c, entity, mtmp, false, mtmp.getCapitalVariableNameSingular()); // foo(OtherType) this.addFluentSetter(c, entity, mtmp, true, mtmp.getCapitalVariableNameSingular()); // foo(OtherTypeBuilder) this.addFluentSetter(c, entity, mtmp, false, "with"); // with(OtherType) this.addFluentSetter(c, entity, mtmp, true, "with"); // with(OtherTypeBuilder) } } private void addFluentSetter(GClass builderCodegen, Entity entity, String variableName, String javaType) { GMethod m = builderCodegen.getMethod(variableName, Argument.arg(javaType, variableName)); m.returnType(entity.getBuilderClassName()); m.body.line("get().set{}({});", Inflector.capitalize(variableName), variableName); m.body.line("return ({}) this;", entity.getBuilderClassName()); } private void addFluentWith(GClass builderCodegen, Entity entity, String variableName, String javaType) { GMethod m = builderCodegen.getMethod("with", Argument.arg(javaType, variableName)); m.returnType(entity.getBuilderClassName()); m.body.line("return {}({});", variableName, variableName); } private void addFluentBuilderSetter(GClass builderCodegen, Entity entity, String variableName, String javaType) { GMethod m = builderCodegen.getMethod(variableName, Argument.arg(javaType, variableName)); m.returnType(entity.getBuilderClassName()); m.body.line("return {}({} == null ? null : {}.get());", variableName, variableName, variableName); } private void addFluentGetter(GClass builderCodegen, String variableName, String javaType) { GMethod m = builderCodegen.getMethod(variableName + "()"); m.returnType(javaType); m.body.line("return get().get{}();", Inflector.capitalize(variableName)); } private void addFluentBuilderGetter(GClass builderCodegen, String variableName, String javaType) { GMethod m = builderCodegen.getMethod(variableName + "()"); m.returnType(javaType + "Builder"); m.body.line("if (get().get{}() == null) {", Inflector.capitalize(variableName)); m.body.line("_ return null;"); m.body.line("}"); m.body.line("return Builders.existing(get().get{}());", Inflector.capitalize(variableName)); } private void addFluentSetter(GClass builderCodegen, Entity entity, ManyToManyProperty mtmp, boolean isForBuilder, String methodName) { final String arg; if (isForBuilder) { arg = mtmp.getTargetTable().getBuilderClassName(); } else { arg = mtmp.getTargetTable().getFullClassName(); } GMethod m = builderCodegen.getMethod(Inflector.uncapitalize(methodName), Argument.arg(arg, mtmp.getVariableName())); m.returnType(entity.getBuilderClassName()); if (isForBuilder) { m.body.line("get().add{}({}.get());", mtmp.getCapitalVariableNameSingular(), mtmp.getVariableName()); } else { m.body.line("get().add{}({});", mtmp.getCapitalVariableNameSingular(), mtmp.getVariableName()); } m.body.line("return ({}) this;", entity.getBuilderClassName()); } private void addToDefaults(GClass c, GMethod defaults, String variableName, String type, String defaultValue) { String defaultMethodName = "default" + StringUtils.capitalize(variableName); defaults.body.line("if ({}() == null) {", variableName); defaults.body.line("_ {}({}());", variableName, defaultMethodName); defaults.body.line("}"); GMethod thisDefault = c.getMethod(defaultMethodName); thisDefault.setProtected().returnType(type).body.line("return {};", defaultValue); } private void addToDefaultsWithContextLookup(GClass c, GMethod defaults, ManyToOneProperty mtop, String defaultValue) { String variableName = mtop.getVariableName(); String defaultMethodName = "default" + StringUtils.capitalize(variableName); defaults.body.line("if ({}() == null) {", variableName); defaults.body.line("_ {}(c.getIfAvailable({}.class));", variableName, mtop.getOneSide().getClassName()); defaults.body.line("_ if ({}() == null) {", variableName); defaults.body.line("_ _ {}({}());", variableName, defaultMethodName); defaults.body.line("_ _ c.rememberIfSet({}());", variableName); defaults.body.line("_ }"); defaults.body.line("}"); GMethod thisDefault = c.getMethod(defaultMethodName); thisDefault.setProtected().returnType(mtop.getJavaType() + "Builder").body.line("return {};", defaultValue); } }