package joist.codegen.passes; import java.util.ArrayList; import java.util.Collections; 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.AbstractChanged; import joist.domain.Changed; import joist.domain.Shim; import joist.domain.orm.ForeignKeyCodeHolder; import joist.domain.orm.ForeignKeyHolder; import joist.domain.orm.ForeignKeyListHolder; import joist.domain.uow.UoW; import joist.domain.validation.rules.MaxLength; import joist.domain.validation.rules.NotEmpty; import joist.domain.validation.rules.NotNull; import joist.sourcegen.Argument; import joist.sourcegen.GClass; import joist.sourcegen.GField; import joist.sourcegen.GMethod; import joist.util.Copy; import joist.util.ListDiff; public class GenerateDomainCodegenPass implements Pass<Codegen> { public void pass(Codegen codegen) { for (Entity entity : codegen.getSchema().getEntities().values()) { if (entity.isCodeEntity()) { continue; } GClass domainCodegen = codegen.getOutputCodegenDirectory().getClass(entity.getFullCodegenClassName()); domainCodegen.setAbstract(); // leave public otherwise reflection signatures are lost domainCodegen.baseClassName(entity.getParentClassName()); domainCodegen.addAnnotation("@SuppressWarnings(\"all\")"); domainCodegen.getConstructor().setProtected().body.line("this.addExtraRules();"); domainCodegen.getMethod("addExtraRules").setPrivate(); this.addQueries(domainCodegen, entity); this.primitiveProperties(domainCodegen, entity); this.manyToOneProperties(domainCodegen, entity); this.oneToManyProperties(domainCodegen, entity); this.manyToManyProperties(domainCodegen, entity); this.changed(domainCodegen, entity); this.clearAssociations(domainCodegen, entity); } } private void addQueries(GClass domainCodegen, Entity entity) { if (!entity.isCodeEntity()) { GField query = domainCodegen.getField("queries").setPublic().setStatic().setFinal(); query.type(entity.getFullQueriesClassName()); domainCodegen.staticInitializer.line("Aliases.{}();", entity.getVariableName()); domainCodegen.staticInitializer.line("queries = new {}Queries();", entity.getClassName()); } } private void primitiveProperties(GClass domainCodegen, Entity entity) { for (PrimitiveProperty p : entity.getPrimitiveProperties()) { GField field = domainCodegen.getField(p.getVariableName()); field.type(p.getJavaType()); field.initialValue(p.getDefaultJavaString()); field.makeGetter(); if (!"version".equals(p.getColumnName())) { GMethod setter = domainCodegen.getMethod("set" + p.getCapitalVariableName()); setter.argument(p.getJavaType(), p.getVariableName()); if ("id".equals(p.getColumnName())) { setter.body.line("if (this.{} != null) {", p.getVariableName()); setter.body.line("_ throw new IllegalStateException(this + \" id cannot be changed\");"); setter.body.line("}"); } setter.body.line("this.getChanged().record(\"{}\", this.{}, {});", p.getVariableName(), p.getVariableName(), p.getVariableName()); setter.body.line("this.{} = {};", p.getVariableName(), p.getVariableName()); if ("id".equals(p.getColumnName())) { setter.body.line("if (UoW.isOpen()) {"); setter.body.line("_ UoW.getIdentityMap().store(this);"); setter.body.line("}"); domainCodegen.addImports(UoW.class); } if (!"id".equals(p.getColumnName())) { GMethod defaultSetter = domainCodegen.getMethod("default{}", p.getCapitalVariableName()).setProtected(); defaultSetter.argument(p.getJavaType(), p.getVariableName()); defaultSetter.body.line("this.{} = {};", p.getVariableName(), p.getVariableName()); } } GClass shims = domainCodegen.getInnerClass("Shims").setPackagePrivate(); GField shimField = shims.getField(p.getVariableName()).setProtected().setStatic().setFinal(); shimField.type("Shim<" + entity.getClassName() + ", " + p.getJavaType() + ">"); GClass shimClass = shimField.initialAnonymousClass(); GMethod shimSetter = shimClass.getMethod("set"); shimSetter.argument(entity.getClassName(), "instance").argument(p.getJavaType(), p.getVariableName()); shimSetter.body.line("(({}) instance).{} = {};", entity.getCodegenClassName(), p.getVariableName(), p.getVariableName()); GMethod shimGetter = shimClass.getMethod("get"); shimGetter.argument(entity.getClassName(), "instance"); shimGetter.returnType(p.getJavaType()); shimGetter.body.line("return (({}) instance).{};", entity.getCodegenClassName(), p.getVariableName()); GMethod shimName = shimClass.getMethod("getName").returnType(String.class); shimName.body.line("return \"{}\";", p.getVariableName()); if (p.shouldHaveNotNullRule()) { domainCodegen.getMethod("addExtraRules").body.line("this.addRule(new NotNull<{}>(Shims.{}));",// entity.getClassName(), p.getVariableName()); domainCodegen.addImports(NotNull.class); } if (p.getMaxCharacterLength() != 0) { GMethod addExtraRules = domainCodegen.getMethod("addExtraRules"); addExtraRules.body.line("this.addRule(new MaxLength<{}>(Shims.{}, {}));",// entity.getClassName(), p.getVariableName(), p.getMaxCharacterLength()); addExtraRules.body.line("this.addRule(new NotEmpty<{}>(Shims.{}));", entity.getClassName(), p.getVariableName()); domainCodegen.addImports(MaxLength.class, NotEmpty.class); } domainCodegen.addImports(Shim.class); } } private void manyToOneProperties(GClass domainCodegen, Entity entity) { for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { GField field = domainCodegen.getField(mtop.getVariableName()).setFinal(); if (mtop.getOneSide().isCodeEntity()) { field.type("ForeignKeyCodeHolder<{}>", mtop.getJavaType()); field.initialValue("new ForeignKeyCodeHolder<{}>({}.class)", mtop.getJavaType(), mtop.getJavaType()); domainCodegen.addImports(ForeignKeyCodeHolder.class); } else { field.type("ForeignKeyHolder<{}, {}>", entity.getClassName(), mtop.getJavaType()); field.initialValue( "new ForeignKeyHolder<{}, {}>({}.class, {}.class, Aliases.{}(), Aliases.{}().{})", entity.getClassName(), mtop.getJavaType(), entity.getClassName(), mtop.getJavaType(), mtop.getOneSide().getVariableName(), entity.getVariableName(), mtop.getVariableName()); domainCodegen.addImports(ForeignKeyHolder.class); } GMethod getter = domainCodegen.getMethod("get" + mtop.getCapitalVariableName()); getter.returnType(mtop.getJavaType()); getter.body.line("return this.{}.get();", mtop.getVariableName()); GMethod setter = domainCodegen.getMethod("set{}", mtop.getCapitalVariableName()); setter.argument(mtop.getJavaType(), mtop.getVariableName()); if (!mtop.getOneSide().isCodeEntity() && !mtop.getOneToManyProperty().isCollectionSkipped()) { setter.body.line("if ({} == this.get{}()) {", mtop.getVariableName(), mtop.getCapitalVariableName()); setter.body.line("_ return;"); setter.body.line("}"); setter.body.line("if (this.{}.get() != null) {", mtop.getVariableName()); setter.body.line("_ this.{}.get().remove{}WithoutPercolation(({}) this);",// mtop.getVariableName(), mtop.getOneToManyProperty().getCapitalVariableNameSingular(), entity.getClassName()); setter.body.line("}"); if (mtop.getOneToManyProperty().isOneToOne()) { setter.body.line("if ({} != null) {", mtop.getVariableName()); setter.body.line("_ {}.set{}(null);", mtop.getVariableName(), mtop.getOneToManyProperty().getCapitalVariableNameSingular()); setter.body.line("}"); } } setter.body.line("this.set{}WithoutPercolation({});", mtop.getCapitalVariableName(), mtop.getVariableName()); if (!mtop.getOneSide().isCodeEntity() && !mtop.getOneToManyProperty().isCollectionSkipped()) { setter.body.line("if (this.{}.get() != null) {", mtop.getVariableName()); setter.body.line("_ this.{}.get().add{}WithoutPercolation(({}) this);",// mtop.getVariableName(), mtop.getOneToManyProperty().getCapitalVariableNameSingular(), entity.getClassName()); setter.body.line("}"); } GMethod setter2 = domainCodegen.getMethod("set{}WithoutPercolation", mtop.getCapitalVariableName()).setProtected(); setter2.argument(mtop.getJavaType(), mtop.getVariableName()); setter2.body.line( "this.getChanged().record(\"{}\", this.{}.get(), {});", mtop.getVariableName(), mtop.getVariableName(), mtop.getVariableName()); setter2.body.line("this.{}.set({});", mtop.getVariableName(), mtop.getVariableName()); if (mtop.getOneSide().isCodeEntity()) { GMethod defaultSetter = domainCodegen.getMethod("default{}", mtop.getCapitalVariableName()).setProtected(); defaultSetter.argument(mtop.getJavaType(), mtop.getVariableName()); defaultSetter.body.line("this.{}.set({});", mtop.getVariableName(), mtop.getVariableName()); } GClass shims = domainCodegen.getInnerClass("Shims"); GField shimField = shims.getField(mtop.getVariableName() + "Id").setProtected().setStatic().setFinal(); shimField.type("Shim<" + entity.getClassName() + ", Long>"); GClass shimClass = shimField.initialAnonymousClass(); GMethod shimSetter = shimClass.getMethod("set"); shimSetter.argument(entity.getClassName(), "instance").argument("Long", mtop.getVariableName() + "Id"); shimSetter.body.line("(({}) instance).{}.setId({}Id);", entity.getCodegenClassName(), mtop.getVariableName(), mtop.getVariableName()); GMethod shimGetter = shimClass.getMethod("get"); shimGetter.argument(entity.getClassName(), "instance"); shimGetter.returnType("Long"); shimGetter.body.line(0, "return (({}) instance).{}.getId();", entity.getCodegenClassName(), mtop.getVariableName()); GMethod shimName = shimClass.getMethod("getName").returnType(String.class); shimName.body.line("return \"{}\";", mtop.getVariableName()); if (mtop.isNotNull()) { domainCodegen.getMethod("addExtraRules").body.line("this.addRule(new NotNull<{}>(Shims.{}Id));",// entity.getClassName(), mtop.getVariableName()); domainCodegen.addImports(NotNull.class); } if (mtop.getOneSide().isCodeEntity()) { CodeEntity c = (CodeEntity) mtop.getOneSide(); for (CodeValue v : c.getCodes()) { GMethod m = domainCodegen.getMethod("is{}", v.getNameCamelCased()).returnType(boolean.class); m.body.line("return get{}() == {}.{};", mtop.getCapitalVariableName(), c.getClassName(), v.getEnumName()); } } domainCodegen.addImports(Shim.class); } } private void oneToManyProperties(GClass domainCodegen, Entity entity) { for (OneToManyProperty otmp : entity.getOneToManyProperties()) { if (otmp.isCollectionSkipped()) { continue; } GField collection = domainCodegen.getField(otmp.getVariableName()).setFinal(); collection.type("ForeignKeyListHolder<{}, {}>", entity.getClassName(), otmp.getTargetJavaType()); collection.initialValue("new ForeignKeyListHolder<{}, {}>(({}) this, Aliases.{}(), Aliases.{}().{}, new {}ListDelegate())",// entity.getClassName(), otmp.getTargetJavaType(), entity.getClassName(), otmp.getManySide().getVariableName(), otmp.getManySide().getVariableName(), otmp.getKeyFieldName(), otmp.getCapitalVariableName()); domainCodegen.addImports(ForeignKeyListHolder.class); if (!otmp.isOneToOne()) { GMethod getter = domainCodegen.getMethod("get" + otmp.getCapitalVariableName()).returnType(otmp.getJavaType()); getter.body.line("return this.{}.get();", otmp.getVariableName()); GMethod setter = domainCodegen.getMethod("set" + otmp.getCapitalVariableName()).argument(otmp.getJavaType(), otmp.getVariableName()); setter.body.line( "ListDiff<{}> diff = ListDiff.of(this.get{}(), {});", otmp.getTargetJavaType(), otmp.getCapitalVariableName(), otmp.getVariableName()); setter.body.line("for ({} o : diff.removed) {", otmp.getTargetJavaType()); setter.body.line("_ this.remove{}(o);", otmp.getCapitalVariableNameSingular()); setter.body.line("}"); setter.body.line("for ({} o : diff.added) {", otmp.getTargetJavaType()); setter.body.line("_ this.add{}(o);", otmp.getCapitalVariableNameSingular()); setter.body.line("}"); setter.body.line("this.{}.set({});", otmp.getVariableName(), otmp.getVariableName()); GMethod adder = domainCodegen.getMethod("add{}", otmp.getCapitalVariableNameSingular()); adder.argument(otmp.getTargetJavaType(), "o"); adder.body.line("if (o.get{}() == this) {", otmp.getManyToOneProperty().getCapitalVariableName()); adder.body.line("_ return;"); adder.body.line("}"); adder.body.line("o.set{}WithoutPercolation(({}) this);", otmp.getManyToOneProperty().getCapitalVariableName(), entity.getClassName()); adder.body.line("this.add{}WithoutPercolation(o);", otmp.getCapitalVariableNameSingular()); GMethod remover = domainCodegen.getMethod("remove{}", otmp.getCapitalVariableNameSingular()); remover.argument(otmp.getTargetJavaType(), "o"); remover.body.line("if (o.get{}() != this) {", otmp.getManyToOneProperty().getCapitalVariableName()); remover.body.line("_ return;"); remover.body.line("}"); remover.body.line("o.set{}WithoutPercolation(null);", otmp.getManyToOneProperty().getCapitalVariableName(), entity.getClassName()); remover.body.line("this.remove{}WithoutPercolation(o);", otmp.getCapitalVariableNameSingular()); if (otmp.isManyToMany()) { // always delete join tables remover.body.line("if (UoW.isOpen()) {"); remover.body.line("_ {}.queries.delete(o);", otmp.getManySide().getClassName()); remover.body.line("}"); domainCodegen.addImports(UoW.class); } else if (otmp.isOwnerMe()) { // otherwise delete if we're the owner remover.body.line("if (UoW.isOpen() && UoW.isImplicitDeletionOfChildrenEnabled()) {"); remover.body.line("_ {}.queries.delete(o);", otmp.getManySide().getClassName()); remover.body.line("}"); domainCodegen.addImports(UoW.class); } if (otmp.isManyToMany()) { getter.setPrivate(); setter.setPrivate(); adder.setPrivate(); remover.setPrivate(); } domainCodegen.addImports(Copy.class, List.class, ListDiff.class); } else { GMethod getter = domainCodegen.getMethod("get" + otmp.getCapitalVariableNameSingular()).returnType(otmp.getTargetJavaType()); getter.body.line("return (this.{}.get().size() == 0) ? null : this.{}.get().get(0);", otmp.getVariableName(), otmp.getVariableName()); GMethod setter = domainCodegen.getMethod("set" + otmp.getCapitalVariableNameSingular()); setter.argument(otmp.getTargetJavaType(), "n"); setter.body.line("{} o = this.get{}();", otmp.getTargetJavaType(), otmp.getCapitalVariableNameSingular()); setter.body.line("if (o == n) {"); setter.body.line("_ return;"); setter.body.line("}"); setter.body.line("if (o != null) {"); setter.body.line("_ o.set{}WithoutPercolation(null);", otmp.getManyToOneProperty().getCapitalVariableName(), entity.getClassName()); setter.body.line("_ this.remove{}WithoutPercolation(o);", otmp.getCapitalVariableNameSingular()); setter.body.line("}"); setter.body.line("if (n != null) {"); setter.body.line("_ n.set{}WithoutPercolation(({}) this);",// otmp.getManyToOneProperty().getCapitalVariableName(), entity.getClassName()); setter.body.line("_ this.add{}WithoutPercolation(n);", otmp.getCapitalVariableNameSingular()); setter.body.line("}"); } GMethod adder2 = domainCodegen.getMethod("add{}WithoutPercolation", otmp.getCapitalVariableNameSingular()); adder2.argument(otmp.getTargetJavaType(), "o").setProtected(); adder2.body.line("this.getChanged().record(\"{}\");", otmp.getVariableName()); adder2.body.line("this.{}.add(o);", otmp.getVariableName()); GMethod remover2 = domainCodegen.getMethod("remove{}WithoutPercolation", otmp.getCapitalVariableNameSingular()); remover2.argument(otmp.getTargetJavaType(), "o").setProtected(); remover2.body.line("this.getChanged().record(\"{}\");", otmp.getVariableName()); remover2.body.line("this.{}.remove(o);", otmp.getVariableName()); GClass listDelegate = domainCodegen.getInnerClass("{}ListDelegate", otmp.getCapitalVariableName()).setPrivate().notStatic(); listDelegate.implementsInterface("joist.domain.util.ListProxy.Delegate<{}>", otmp.getTargetJavaType()); GMethod doAdd = listDelegate.getMethod("doAdd", Argument.arg(otmp.getTargetJavaType(), "e")); GMethod doRemove = listDelegate.getMethod("doRemove", Argument.arg(otmp.getTargetJavaType(), "e")); if (!otmp.isOneToOne()) { doAdd.body.line("add{}(e);", otmp.getCapitalVariableNameSingular()); doRemove.body.line("remove{}(e);", otmp.getCapitalVariableNameSingular()); } else { doAdd.body.line("throw new UnsupportedOperationException(\"Not implemented\");"); doRemove.body.line("throw new UnsupportedOperationException(\"Not implemented\");"); } domainCodegen.addImports(otmp.getManySide().getFullAliasClassName()); } } private void manyToManyProperties(GClass domainCodegen, Entity entity) { for (ManyToManyProperty mtmp : entity.getManyToManyProperties()) { if (mtmp.getMySideOneToMany().isCollectionSkipped()) { continue; } GMethod getter = domainCodegen.getMethod("get" + mtmp.getCapitalVariableName()).returnType(mtmp.getJavaType()); getter.body.line("{} l = {};", mtmp.getJavaType(), mtmp.getDefaultJavaString()); getter.body.line("for ({} o : this.get{}()) {",// mtmp.getJoinTable().getClassName(), mtmp.getMySideManyToOne().getOneToManyProperty().getCapitalVariableName()); getter.body.line("_ l.add(o.get{}());", mtmp.getCapitalVariableNameSingular()); getter.body.line("}"); getter.body.line("return Collections.unmodifiableList(l);"); domainCodegen.addImports(Collections.class); GMethod setter = domainCodegen.getMethod("set" + mtmp.getCapitalVariableName()).argument(mtmp.getJavaType(), mtmp.getVariableName()); setter.body.line( "ListDiff<{}> diff = ListDiff.of(this.get{}(), {});", mtmp.getTargetJavaType(), mtmp.getCapitalVariableName(), mtmp.getVariableName()); setter.body.line("for ({} o : diff.removed) {", mtmp.getTargetJavaType()); setter.body.line("_ this.remove{}(o);", mtmp.getCapitalVariableNameSingular()); setter.body.line("}"); setter.body.line("for ({} o : diff.added) {", mtmp.getTargetJavaType()); setter.body.line("_ this.add{}(o);", mtmp.getCapitalVariableNameSingular()); setter.body.line("}"); GMethod adder = domainCodegen.getMethod("add{}", mtmp.getCapitalVariableNameSingular()); adder.argument(mtmp.getTargetTable().getClassName(), "o"); adder.body.line("{} a = new {}();", mtmp.getJoinTable().getClassName(), mtmp.getJoinTable().getClassName()); adder.body.line("a.set{}(({}) this);", mtmp.getMySideManyToOne().getCapitalVariableName(), entity.getClassName()); adder.body.line("a.set{}(o);", mtmp.getOther().getMySideManyToOne().getCapitalVariableName(), mtmp.getTargetTable().getClassName()); GMethod remover = domainCodegen.getMethod("remove{}", mtmp.getCapitalVariableNameSingular()); remover.argument(mtmp.getTargetTable().getClassName(), "o"); remover.body.line("for ({} a : Copy.list(this.get{}())) {",// mtmp.getJoinTable().getClassName(), mtmp.getMySideManyToOne().getOneToManyProperty().getCapitalVariableName()); remover.body.line("_ if (a.get{}().equals(o)) {", mtmp.getCapitalVariableNameSingular()); remover.body.line("_ _ a.set{}(null);", mtmp.getCapitalVariableNameSingular()); remover.body.line("_ _ a.set{}(null);", mtmp.getOther().getCapitalVariableNameSingular()); remover.body.line("_ _ if (UoW.isOpen()) {"); remover.body.line("_ _ _ UoW.delete(a);"); remover.body.line("_ _ }"); remover.body.line("_ }"); remover.body.line("}"); domainCodegen.addImports(Copy.class, ArrayList.class, UoW.class, ListDiff.class); } } private void changed(GClass domainCodegen, Entity entity) { if (entity.isRoot()) { domainCodegen.getField("changed").type(Changed.class).setProtected(); } GMethod getter = domainCodegen.getMethod("getChanged").returnType("{}Changed", entity.getClassName()); getter.body.line("if (this.changed == null) {"); getter.body.line("_ this.changed = new {}Changed(({}) this);", entity.getClassName(), entity.getClassName()); getter.body.line("}"); getter.body.line("return ({}Changed) this.changed;", entity.getClassName()); GClass changedClass = domainCodegen.getInnerClass("{}Changed", entity.getClassName()); if (entity.isRoot()) { changedClass.baseClass(AbstractChanged.class); } else { changedClass.baseClassName("{}Changed", entity.getBaseEntity().getClassName()); } changedClass.getConstructor(entity.getClassName() + " instance").body.line("super(instance);", entity.getClassName()); for (PrimitiveProperty p : entity.getPrimitiveProperties()) { GMethod has = changedClass.getMethod("has{}", p.getCapitalVariableName()).returnType(boolean.class); has.body.line("return this.contains(\"{}\");", p.getVariableName()); GMethod original = changedClass.getMethod("getOriginal{}", p.getCapitalVariableName()).returnType(p.getJavaType()); original.body.line("return ({}) this.getOriginal(\"{}\");", p.getJavaType(), p.getVariableName()); } for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { GMethod has = changedClass.getMethod("has{}", mtop.getCapitalVariableName()).returnType(boolean.class); has.body.line("return this.contains(\"{}\");", mtop.getVariableName()); GMethod original = changedClass.getMethod("getOriginal{}", mtop.getCapitalVariableName()).returnType(mtop.getJavaType()); original.body.line("return ({}) this.getOriginal(\"{}\");", mtop.getJavaType(), mtop.getVariableName()); } for (OneToManyProperty otmp : entity.getOneToManyProperties()) { if (otmp.isCollectionSkipped()) { continue; } GMethod has = changedClass.getMethod("has{}", otmp.getCapitalVariableName()).returnType(boolean.class); has.body.line("return this.contains(\"{}\");", otmp.getVariableName()); } } private void clearAssociations(GClass domainCodegen, Entity entity) { GMethod clearAssociations = domainCodegen.getMethod("clearAssociations"); clearAssociations.addAnnotation("@Override"); clearAssociations.body.line("super.clearAssociations();"); for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { clearAssociations.body.line("this.set{}(null);", mtop.getCapitalVariableName()); } for (OneToManyProperty otmp : entity.getOneToManyProperties()) { if (otmp.isCollectionSkipped()) { continue; } if (otmp.isOneToOne()) { clearAssociations.body.line("this.set{}(null);", otmp.getCapitalVariableNameSingular()); } else { clearAssociations.body.line("for ({} o : Copy.list(this.get{}())) {", otmp.getTargetJavaType(), otmp.getCapitalVariableName()); clearAssociations.body.line("_ remove{}(o);", otmp.getCapitalVariableNameSingular()); clearAssociations.body.line("}"); domainCodegen.addImports(Copy.class, List.class); } } } }