package joist.codegen.passes; import java.util.ArrayList; import java.util.List; import joist.codegen.Codegen; import joist.codegen.dtos.Entity; import joist.codegen.dtos.ManyToOneProperty; import joist.codegen.dtos.PrimitiveProperty; import joist.domain.DomainObject; import joist.domain.orm.AliasRegistry; import joist.domain.orm.queries.Alias; import joist.domain.orm.queries.JoinClause; import joist.domain.orm.queries.columns.AliasColumn; import joist.domain.orm.queries.columns.CodeAliasColumn; import joist.domain.orm.queries.columns.ForeignKeyAliasColumn; import joist.domain.orm.queries.columns.IdAliasColumn; import joist.domain.orm.queries.columns.LongAliasColumn; import joist.sourcegen.Argument; import joist.sourcegen.GClass; import joist.sourcegen.GField; import joist.sourcegen.GMethod; import joist.util.Interpolate; import joist.util.TopologicalSort; import joist.util.TopologicalSort.CycleException; public class GenerateAliasesPass implements Pass<Codegen> { public void pass(Codegen codegen) { GClass aliasesClass = codegen.getOutputCodegenDirectory().getClass(codegen.getConfig().getDomainObjectPackage() + ".Aliases"); aliasesClass.addImports(AliasRegistry.class); List<Entity> sorted; if (codegen.getConfig().db.isMySQL()) { sorted = this.getEntitiesSortedByForeignKeys(codegen); } else { sorted = new ArrayList<Entity>(); // pg doesn't need fk order } for (Entity entity : codegen.getSchema().getEntities().values()) { if (entity.isCodeEntity()) { continue; } GClass aliasClass = codegen.getOutputCodegenDirectory().getClass(entity.getFullAliasClassName()); aliasClass.baseClassName("Alias<{}>", entity.getClassName()); aliasClass.addImports(Alias.class); this.addColumnsFieldAndGetter(aliasClass, entity); this.addIdAndVersionAndSubClassIdMethods(aliasClass, entity); this.addConstructors(aliasClass, entity); this.addPrimitiveColumns(aliasClass, entity); this.addManyToOneColumns(codegen, aliasClass, entity); this.addInheritedColumns(aliasClass, entity); this.addOrderMethod(aliasClass, sorted.indexOf(entity)); this.addJoinOnMethods(aliasClass, entity); this.addAliasesField(aliasesClass, entity); } } private void addInheritedColumns(GClass aliasClass, Entity entity) { boolean first = true; Entity baseEntity = entity.getBaseEntity(); while (baseEntity != null) { if (first) { GField baseAliasField = aliasClass.getField("baseAlias").setFinal(); baseAliasField.type(baseEntity.getFullAliasClassName()); GMethod baseClassAliasGetter = aliasClass.getMethod("getBaseClassAlias"); baseClassAliasGetter.returnType("Alias<{}>", baseEntity.getClassName()); baseClassAliasGetter.body.line("return this.baseAlias;"); first = false; } for (PrimitiveProperty p : baseEntity.getPrimitiveProperties()) { GField field = aliasClass.getField(p.getVariableName()).setPublic().setFinal(); field.type("{}<{}>", p.getAliasColumnClassName(), baseEntity.getClassName()); this.appendToConstructors(aliasClass, "this.{} = this.baseAlias.{};", p.getVariableName(), p.getVariableName()); } for (ManyToOneProperty p : baseEntity.getManyToOneProperties()) { Class<?> aliasColumnClass = (p.getOneSide().isCodeEntity()) ? CodeAliasColumn.class : ForeignKeyAliasColumn.class; aliasClass.addImports(aliasColumnClass); aliasClass.addImports(p.getOneSide().getFullClassName()); GField field = aliasClass.getField(p.getVariableName()).setPublic().setFinal(); field.type("{}<{}, {}>", aliasColumnClass.getSimpleName(), p.getManySide().getClassName(), p.getJavaType()); this.appendToConstructors(aliasClass, "this.{} = this.baseAlias.{};", p.getVariableName(), p.getVariableName()); } aliasClass.addImports(entity.getConfig().getDomainObjectPackage() + "." + baseEntity.getClassName()); baseEntity = baseEntity.getBaseEntity(); } } private void addColumnsFieldAndGetter(GClass aliasClass, Entity entity) { GField columns = aliasClass.getField("columns").setFinal(); columns.type("List<AliasColumn<{}, ?, ?>>", entity.getClassName()); columns.initialValue("new ArrayList<AliasColumn<{}, ?, ?>>()", entity.getClassName()); GMethod columnsGetter = aliasClass.getMethod("getColumns"); columnsGetter.returnType("List<AliasColumn<{}, ?, ?>>", entity.getClassName()); columnsGetter.body.line("return this.columns;"); } private void addIdAndVersionAndSubClassIdMethods(GClass aliasClass, Entity entity) { Entity rootEntity = entity.getRootEntity(); GMethod idColumnMethod = aliasClass.getMethod("getIdColumn"); idColumnMethod.returnType("IdAliasColumn<{}>", rootEntity.getClassName()); idColumnMethod.body.line("return this.id;"); GMethod versionColumnMethod = aliasClass.getMethod("getVersionColumn"); versionColumnMethod.returnType("LongAliasColumn<{}>", rootEntity.getClassName()); versionColumnMethod.body.line("return this.version;"); if (entity.getBaseEntity() == null) { GMethod subclassIdColumnMethod = aliasClass.getMethod("getSubClassIdColumn"); subclassIdColumnMethod.returnType("IdAliasColumn<{}>", entity.getClassName()); subclassIdColumnMethod.body.line("return null;"); } else { GMethod subclassIdColumnMethod = aliasClass.getMethod("getSubClassIdColumn"); subclassIdColumnMethod.returnType("IdAliasColumn<{}>", entity.getClassName()); subclassIdColumnMethod.body.line("return this.subClassId;"); GField subClassIdField = aliasClass.getField("subClassId").setFinal(); subClassIdField.type("IdAliasColumn<{}>", entity.getClassName()); subClassIdField.initialValue("new IdAliasColumn<{}>(this, \"id\", null)", entity.getClassName()); } } private void addConstructors(GClass aliasClass, Entity entity) { GMethod cstr0 = aliasClass.getConstructor(); cstr0.body.line("this(\"{}\", null, true);", entity.getAliasAlias()); GMethod cstr1 = aliasClass.getConstructor("String alias"); cstr1.body.line("this(alias, null, true);"); GMethod cstr2; if (entity.getBaseEntity() != null) { cstr2 = aliasClass.getConstructor("String alias", entity.getBaseEntity().getAliasName() + " baseAlias", "boolean addSubClasses"); } else { cstr2 = aliasClass.getConstructor("String alias", "Object noopBaseAlias", "boolean addSubClasses"); } cstr2.body.line("super({}.class, \"{}\", alias);", entity.getClassName(), entity.getTableName()); // If any sub-classes, we want to know about their sub-aliases to instantiate during SELECTs if (entity.getBaseEntity() != null) { cstr2.body.line("this.baseAlias = (baseAlias != null) ? baseAlias : new {}Alias(alias + \"_b\", null, false);",// entity.getBaseEntity().getClassName()); } if (entity.getSubEntities().size() > 0) { cstr2.body.line("{} {} = this;", entity.getAliasName(), entity.getVariableName()); cstr2.body.line("if (addSubClasses) {"); int i = 0; for (Entity subEntity : entity.getSubEntitiesRecursively()) { cstr2.body.line(" {} {} = new {}(alias + \"_{}\", {}, false);",// subEntity.getAliasName(), subEntity.getVariableName(), subEntity.getAliasName(), i++, subEntity.getBaseEntity().getVariableName()); cstr2.body.line(" this.addSubClassAlias({});", subEntity.getVariableName()); aliasClass.addImports(subEntity.getFullAliasClassName()); } cstr2.body.line("}"); } aliasClass.addImports(ArrayList.class, List.class, Alias.class, AliasColumn.class, IdAliasColumn.class, LongAliasColumn.class); aliasClass.addImports(entity.getFullClassName()); } private void addPrimitiveColumns(GClass aliasClass, Entity entity) { for (PrimitiveProperty p : entity.getPrimitiveProperties()) { GField field = aliasClass.getField(p.getVariableName()).setPublic().setFinal(); field.type("{}<{}>", p.getAliasColumnClassName(), entity.getClassName()); field.initialValue("new {}(this, \"{}\", {}.Shims.{})",// field.getTypeClassName(), p.getColumnName(), entity.getCodegenClassName(), p.getVariableName()); this.appendToConstructors(aliasClass, "this.columns.add(this.{});", p.getVariableName()); } } private void addManyToOneColumns(Codegen codegen, GClass aliasClass, Entity entity) { for (ManyToOneProperty p : entity.getManyToOneProperties()) { Class<?> aliasColumnClass = (p.getOneSide().isCodeEntity()) ? CodeAliasColumn.class : ForeignKeyAliasColumn.class; aliasClass.addImports(aliasColumnClass); aliasClass.addImports(p.getOneSide().getFullClassName()); GField field = aliasClass.getField(p.getVariableName()).setPublic().setFinal(); field.type("{}<{}, {}>", aliasColumnClass.getSimpleName(), entity.getClassName(), p.getJavaType()); field.initialValue("new {}<{}, {}>(this, \"{}\", {}.Shims.{}Id)",// aliasColumnClass.getSimpleName(), entity.getClassName(), p.getJavaType(), p.getColumnName(), entity.getCodegenClassName(), p.getVariableName()); if (!p.getOneSide().isCodeEntity()) { } this.appendToConstructors(aliasClass, "this.columns.add(this.{});", p.getVariableName()); } } private void appendToConstructors(GClass aliasClass, String line, Object... args) { line = Interpolate.string(line, args); for (GMethod constructor : aliasClass.getConstructors()) { if (!constructor.hasSameArguments("String alias") && constructor.hasArguments()) { constructor.body.line(line); } } } private List<Entity> getEntitiesSortedByForeignKeys(Codegen codegen) { TopologicalSort<Entity> ts = new TopologicalSort<Entity>(); for (Entity entity : codegen.getSchema().getEntities().values()) { if (!entity.isCodeEntity()) { ts.addNode(entity); } } try { for (Entity entity : codegen.getSchema().getEntities().values()) { // subclasses must come after their base class if (entity.isSubclass()) { ts.addDependency(entity, entity.getBaseEntity()); } // not-null foreign keys must come before their target table for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { if (mtop.isNotNull() && !mtop.getOneSide().isCodeEntity()) { // make sure our subclasses are added as dependencies as well for (Entity current : mtop.getOneSide().getAllBaseAndSubEntities()) { ts.addDependency(entity, current); } } } } } catch (CycleException ce) { String message = ce.getMessage() + ". Note: due to MySQL lacking deferred foreign key constraints," + " Joist determines entity INSERT order at buildtime. For this to work, you cannot have any" + " foreign key cycles in your schema. I.e. make one of the columns nullable. Or use PostgreSQL."; throw new RuntimeException(message); } for (Entity entity : codegen.getSchema().getEntities().values()) { for (ManyToOneProperty mtop : entity.getManyToOneProperties()) { if (!mtop.isNotNull() && !mtop.getOneSide().isCodeEntity()) { ts.addDependencyIfNoCycle(entity, mtop.getOneSide()); } } } return ts.get(); } private void addOrderMethod(GClass aliasClass, int index) { GMethod order = aliasClass.getMethod("getOrder").returnType(int.class); order.body.line("return {};", index); } private void addJoinOnMethods(GClass aliasClass, Entity entity) { String javaType = entity.getClassName(); GMethod on = aliasClass.getMethod("on", Argument.arg("ForeignKeyAliasColumn<T, " + javaType + ">", "on")); on.typeParameters("T extends DomainObject"); on.returnType("JoinClause<T, {}>", javaType); on.body.line("return new JoinClause<T, {}>(\"INNER JOIN\", this, on);", javaType); aliasClass.addImports(ForeignKeyAliasColumn.class, JoinClause.class, DomainObject.class); GMethod leftOn = aliasClass.getMethod("leftOn", Argument.arg("ForeignKeyAliasColumn<T, " + javaType + ">", "on")); leftOn.typeParameters("T extends DomainObject"); leftOn.returnType("JoinClause<T, {}>", javaType); leftOn.body.line("return new JoinClause<T, {}>(\"LEFT OUTER JOIN\", this, on);", javaType); aliasClass.addImports(ForeignKeyAliasColumn.class, JoinClause.class, DomainObject.class); } private void addAliasesField(GClass aliasesClass, Entity entity) { aliasesClass.getField(entity.getVariableName()).type(entity.getAliasName()).setStatic(); GMethod method = aliasesClass.getMethod(entity.getVariableName()).returnType(entity.getAliasName()).setStatic(); method.body.line("if ({} == null) {", entity.getVariableName()); method.body.line("_ {} = new {}();", entity.getVariableName(), entity.getAliasName()); method.body.line("_ AliasRegistry.register({}.class, {});", entity.getClassName(), entity.getVariableName()); method.body.line("}"); method.body.line("return {};", entity.getVariableName()); } }