package pt.ist.fenixframework.pstm.dml; import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import pt.ist.fenixframework.pstm.ToSqlConverter; import pt.ist.fenixframework.pstm.repository.DbUtil; import dml.CodeGenerator; import dml.CompilerArgs; import dml.DomainClass; import dml.DomainEntity; import dml.DomainModel; import dml.ExternalizationElement; import dml.Role; import dml.Slot; import dml.ValueType; public class FenixCodeGenerator extends CodeGenerator { protected static final String TO_SQL_CONVERTER_CLASS = ToSqlConverter.class.getName(); protected static final String RESULT_SET_READER_CLASS = "pt.ist.fenixframework.pstm.ResultSetReader"; protected static final String IMPORTS_COMMA_SEPARATED = "pt.ist.fenixframework.pstm.VBox,pt.ist.fenixframework.pstm.RelationList,pt.ist.fenixframework.pstm.OJBFunctionalSetWrapper,pt.ist.fenixframework.ValueTypeSerializationGenerator.*"; protected static final String DOMAIN_CLASS_ROOT = "pt.ist.fenixframework.pstm.AbstractDomainObject"; protected static final String DIRECT_RELATION_TYPE_CLASS = "pt.ist.fenixframework.pstm.LoggingRelation"; protected static final String TRANSACTION_CLASS = "pt.ist.fenixframework.pstm.Transaction"; public FenixCodeGenerator(CompilerArgs compArgs, DomainModel domainModel) { super(compArgs, domainModel); } @Override protected String getDomainClassRoot() { return DOMAIN_CLASS_ROOT; } protected String[] getImportFiles() { return IMPORTS_COMMA_SEPARATED.split(","); } @Override protected void generateFilePreamble(String subPackageName, PrintWriter out) { generatePackageDecl(subPackageName, out); for (String importFile : getImportFiles()) { println(out, "import " + importFile + ";"); } } @Override protected void generateBaseClassBody(DomainClass domClass, PrintWriter out) { super.generateBaseClassBody(domClass, out); generateCheckDisconnected(domClass, out); generateDatabaseReader(domClass, out); } @Override protected void generateBaseClassConstructorsBody(DomainClass domClass, PrintWriter out) { super.generateBaseClassConstructorsBody(domClass, out); final Slot ojbConcreteClassSlot = domClass.findSlot("ojbConcreteClass"); if (ojbConcreteClassSlot != null && calculateHierarchyLevel(domClass) == 1) { newline(out); print(out, "setOjbConcreteClass(getClass().getName());"); } } private int calculateHierarchyLevel(DomainEntity domainEntity) { return hasSuperclass(domainEntity) ? calculateHierarchyLevel(((DomainClass) domainEntity).getSuperclass()) + 1 : 0; } private boolean hasSuperclass(DomainEntity domainEntity) { return domainEntity != null && domainEntity instanceof DomainClass && ((DomainClass) domainEntity).getSuperclass() != null; } @Override protected String getDirectRelationType() { return DIRECT_RELATION_TYPE_CLASS; } @Override protected void generateStaticRelationSlots(Role role, PrintWriter out) { super.generateStaticRelationSlots(role, out); if (role.isFirstRole() || (role.getOtherRole().getName() == null)) { String relationName = getRelationSlotNameFor(role); // set the relationName of the LoggingRelation object newline(out); printWords(out, "static"); newBlock(out); printWords(out, relationName); print(out, ".setRelationName(\""); print(out, getTypeFullName(role.getOtherRole().getType())); print(out, "."); print(out, role.getRelation().getName()); print(out, "\");"); if ((role.getMultiplicityUpper() != 1) && (role.getOtherRole().getMultiplicityUpper() != 1)) { // a relation many-to-many need a listener... Role otherRole = role.getOtherRole(); String firstType = getTypeFullName(otherRole.getType()); String secondType = getTypeFullName(role.getType()); newline(out); printWords(out, relationName); print(out, ".addListener(new "); print(out, makeGenericType("dml.runtime.RelationAdapter", firstType, secondType)); print(out, "()"); newBlock(out); println(out, "@Override"); printMethod(out, "public", "void", "beforeAdd", makeArg(firstType, "arg0"), makeArg(secondType, "arg1")); startMethodBody(out); generateRelationRegisterCall("addRelationTuple", role, otherRole, out); endMethodBody(out); println(out, "@Override"); printMethod(out, "public", "void", "beforeRemove", makeArg(firstType, "arg0"), makeArg(secondType, "arg1")); startMethodBody(out); generateRelationRegisterCall("removeRelationTuple", role, otherRole, out); endMethodBody(out); closeBlock(out); print(out, ");"); } // close the static block closeBlock(out); } } protected void generateRelationRegisterCall(String regMethodName, Role r0, Role r1, PrintWriter out) { String r0name = r0.getName(); String r1name = r1.getName(); print(out, TRANSACTION_CLASS); print(out, "."); print(out, regMethodName); print(out, "(\""); print(out, getEntityFullName(r0.getRelation())); print(out, "\", arg1, \""); print(out, (r1name == null) ? "" : r1name); print(out, "\", arg0, \""); print(out, (r0name == null) ? "" : r0name); print(out, "\");"); } @Override protected void generateSetterBody(String setterName, String slotName, String typeName, PrintWriter out) { if (!setterName.startsWith("set$")) { print(out, getSlotExpression(slotName)); print(out, ".put(this, \""); print(out, slotName); print(out, "\", "); print(out, slotName); print(out, ");"); } else { super.generateSetterBody(setterName, slotName, typeName, out); } } @Override protected void generateInitInstance(DomainClass domClass, PrintWriter out) { // generate initInstance method to be used by OJB onNewline(out); newline(out); printMethod(out, "private", "void", "initInstance"); startMethodBody(out); print(out, "initInstance(true);"); endMethodBody(out); super.generateInitInstance(domClass, out); } @Override protected void generateInitializePrimitiveIfNeeded(Slot slot, PrintWriter out) { super.generateInitializePrimitiveIfNeeded(slot, out); if (findWrapperEntry(slot.getTypeName()) == null) { generateSlotInitialization(slot.getName(), out); } } protected void generateSlotInitialization(String name, PrintWriter out) { // This method generates the code that initializes all of the // non-primitive slots to null, so that the corresponding vbox // appears in the TxIntrospector.getWriteSetLog() set of // entries. This will probably disappear once we have the // HBase-based version of the fenix-framework, where each box // knows to which object and attribute it belongs to. If we // had that, the getWriteSetLog method could use the JVSTM // write-set instead of the DBChanges.attrChangeLogs. This, // of course, assuming that creating a box always calls its // put method, with the consequence of making that box go into // the write-set. Even though I've not checked this rigth // now, I strongly believe that's the behavior of the JVSTM. onNewline(out); print(out, "if (!allocateOnly) this."); print(out, name + ".put(this, \"" + name); println(out, "\", null);"); } @Override protected String getNewSlotExpression(Slot slot) { return "VBox.makeNew(this, \"" + slot.getName() + "\", allocateOnly, false)"; } @Override protected String getNewRoleOneSlotExpression(Role role) { return "VBox.makeNew(this, \"" + role.getName() + "\", allocateOnly, false)"; } @Override protected String getNewRoleStarSlotExpression(Role role) { StringBuilder buf = new StringBuilder(); // generate the relation aware collection String thisType = getTypeFullName(role.getOtherRole().getType()); buf.append("new "); buf.append(getRelationAwareTypeFor(role)); buf.append("(("); buf.append(thisType); buf.append(")this, "); buf.append(getRelationSlotNameFor(role)); buf.append(", \""); buf.append(role.getName()); buf.append("\", allocateOnly)"); return buf.toString(); } @Override protected String getRelationAwareBaseTypeFor(Role role) { // FIXME: handle other types of collections other than sets return "RelationList"; } @Override protected String getBoxBaseType() { return "VBox"; } @Override protected String getRoleArgs(Role role) { String args = super.getRoleArgs(role); if ((role.getName() != null) && (role.getMultiplicityUpper() == 1)) { if (args.length() > 0) { args += ", "; } args += "\"" + role.getName() + "\""; } return args; } @Override protected String getRoleOneBaseType() { return "dml.runtime.RoleOneFenix"; } @Override protected void generateGetterBody(String slotName, String typeName, PrintWriter out) { print(out, "pt.ist.fenixframework.pstm.DataAccessPatterns.noteGetAccess(this, \""); print(out, slotName); println(out, "\");"); print(out, "return "); generateGetSlotExpression(slotName, out); print(out, ";"); } protected void generateGetSlotExpression(String slotName, PrintWriter out) { print(out, getSlotExpression(slotName)); print(out, ".get(this, \""); print(out, slotName); print(out, "\")"); } protected void generateRelationGetter(String getterName, Role role, PrintWriter out) { String paramListType = makeGenericType("java.util.List", getTypeFullName(role.getType())); generateRelationGetter(role, paramListType, out); } protected void generateRelationGetter(Role role, String paramListType, PrintWriter out) { generateRelationGetter("get" + capitalize(role.getName()), getSlotExpression(role.getName()), paramListType, out); } protected void generateRelationGetter(String getterName, String valueToReturn, String typeName, PrintWriter out) { newline(out); printFinalMethod(out, "public", typeName, getterName); startMethodBody(out); print(out, "return "); print(out, valueToReturn); print(out, ";"); endMethodBody(out); } @Override protected void generateInitRoleSlot(Role role, PrintWriter out) { super.generateInitRoleSlot(role, out); if (role.getMultiplicityUpper() == 1) { generateSlotInitialization(role.getName(), out); } } @Override protected void generateRoleSlotMethodsMultStar(Role role, PrintWriter out) { super.generateRoleSlotMethodsMultStar(role, out); generateRoleSlotMethodsMultStarGettersAndIterators(role, out); } protected void generateRoleSlotMethodsMultStarGettersAndIterators(Role role, PrintWriter out) { generateRelationGetter("get" + capitalize(role.getName()), role, out); generateOJBSetter(role.getName(), "OJBFunctionalSetWrapper", out); generateIteratorMethod(role, out); } protected void generateIteratorMethod(Role role, PrintWriter out) { generateIteratorMethod(role, out, getSlotExpression(role.getName())); } protected void generateIteratorMethod(Role role, PrintWriter out, final String slotAccessExpression) { newline(out); printFinalMethod(out, "public", makeGenericType("java.util.Iterator", getTypeFullName(role.getType())), "get" + capitalize(role.getName()) + "Iterator"); startMethodBody(out); printWords(out, "return", slotAccessExpression); print(out, ".iterator();"); endMethodBody(out); } @Override protected void generateSlotAccessors(Slot slot, PrintWriter out) { super.generateSlotAccessors(slot, out); generateExternalizationGetter(slot.getName(), slot.getSlotType(), out); generateInternalizationSetter(slot.getName(), slot.getSlotType(), out); } protected void generateExternalizationGetter(String name, ValueType type, PrintWriter out) { newline(out); ValueType vt = getExternalizationType(type); String returnType = getSqlTypeName(type); printFinalMethod(out, "private", returnType, "get$" + name); startMethodBody(out); // handle nulls (if the value is null, then return null) print(out, type.getFullname()); print(out, " value = "); generateGetSlotExpression(name, out); println(out, ";"); print(out, "return "); if (FenixDomainModel.isNullableType(type)) { print(out, "(value == null) ? null : "); } print(out, getExternalizationExpression(type)); print(out, ";"); endMethodBody(out); } protected String getExternalizationExpression(ValueType vt) { StringBuilder expression = new StringBuilder(); // start with the variable holding the slot value (not null) expression.append("value"); // now, go through the externalization elements, externalizing this // value while (!(vt.isBuiltin() || vt.isEnum())) { List<ExternalizationElement> extElems = vt.getExternalizationElements(); if (extElems.size() != 1) { throw new Error("Can't handle value-types with more than one externalization element yet..."); } ExternalizationElement extElem = extElems.get(0); String extMethodName = extElem.getMethodName(); if (extMethodName.contains(".")) { // a static method expression.insert(0, extMethodName + "("); expression.append(")"); } else { // a class-member method expression.append("."); expression.append(extMethodName); expression.append("()"); } vt = extElem.getType(); } // wrap the expression with the final converter method call // note that this is being constructed backwards... if (vt.isEnum()) { expression.insert(0, "Enum("); } else { expression.insert(0, vt.getDomainName() + "("); } expression.insert(0, ".getValueFor"); expression.insert(0, TO_SQL_CONVERTER_CLASS); // close the wrap-up expression.append(")"); return expression.toString(); } protected void generateInternalizationSetter(String name, ValueType type, PrintWriter out) { newline(out); print(out, "private final void set$"); print(out, name); print(out, "("); ValueType vt = getExternalizationType(type); print(out, vt.getFullname()); print(out, " arg0, int txNumber)"); startMethodBody(out); print(out, "this."); print(out, name); print(out, ".persistentLoad("); if (FenixDomainModel.isNullableType(vt)) { print(out, "(arg0 == null) ? null : "); } print(out, getRsReaderExpression(type)); print(out, ", txNumber);"); endMethodBody(out); } protected ValueType getExternalizationType(ValueType vt) { while (!(vt.isBuiltin() || vt.isEnum())) { List<ExternalizationElement> extElems = vt.getExternalizationElements(); if (extElems.size() != 1) { throw new Error("Can't handle value-types with more than one externalization element yet..."); } ExternalizationElement extElem = extElems.get(0); vt = extElem.getType(); } return vt; } protected String getSqlTypeName(ValueType vt) { ValueType extType = getExternalizationType(vt); String toSqlMethodName = "getValueFor" + (extType.isEnum() ? "Enum" : extType.getDomainName()); for (Method m : ToSqlConverter.class.getDeclaredMethods()) { if (m.getName().equals(toSqlMethodName)) { return m.getReturnType().getName(); } } throw new Error("Something's wrong. Couldn't find the appropriate base value type."); } protected String getRsReaderExpression(ValueType type) { StringBuilder buf = new StringBuilder(); buildReconstructionExpression(buf, type, 0); return buf.toString(); } protected int buildReconstructionExpression(StringBuilder buf, ValueType vt, int colNum) { // first, check if is a built-in value type // if it is, then process it and return if (vt.isBuiltin()) { buf.append("arg" + colNum); return colNum + 1; } // it is not built-in, process it normally String intMethodName = vt.getInternalizationMethodName(); // if no internalizationMethodName is present, then use the constructor if (intMethodName == null) { buf.append("new "); buf.append(vt.getFullname()); } else { if (!intMethodName.contains(".")) { // assume that non-dotted names correspond to static methods of // the ValueType vt buf.append(vt.getFullname()); buf.append("."); } buf.append(intMethodName); } buf.append("("); for (ExternalizationElement extElem : vt.getExternalizationElements()) { if (colNum > 0) { buf.append(", "); } colNum = buildReconstructionExpression(buf, extElem.getType(), colNum); } buf.append(")"); return colNum; } protected void generateOJBSetter(String slotName, String typeName, PrintWriter out) { newline(out); printFinalMethod(out, "public", "void", "set$" + slotName, makeArg(typeName, slotName)); startMethodBody(out); printWords(out, getSlotExpression(slotName)); print(out, ".setFromOJB(this, \""); print(out, slotName); print(out, "\", "); print(out, slotName); print(out, ");"); endMethodBody(out); } @Override protected void generateRoleSlotMethodsMultOne(Role role, PrintWriter out) { super.generateRoleSlotMethodsMultOne(role, out); String typeName = getTypeFullName(role.getType()); String slotName = role.getName(); // generateGetter("public", "get$" + slotName, slotName, typeName, out); // generateOJBSetter(slotName, typeName, out); generateOidOJBGetter(slotName, out); } protected void generateOidOJBGetter(String name, PrintWriter out) { newline(out); printMethod(out, "private", "java.lang.Long", "get$oid" + capitalize(name)); startMethodBody(out); print(out, DOMAIN_CLASS_ROOT); print(out, " value = "); generateGetSlotExpression(name, out); println(out, ";"); print(out, "return (value == null) ? null : value.getOid();"); endMethodBody(out); } protected void generateCheckDisconnected(DomainClass domClass, PrintWriter out) { newline(out); printMethod(out, "protected", "void", "checkDisconnected"); startMethodBody(out); if (domClass.hasSuperclass()) { println(out, "super.checkDisconnected();"); } Iterator<Role> roleSlotsIter = domClass.getRoleSlots(); while (roleSlotsIter.hasNext()) { Role role = roleSlotsIter.next(); if (role.getName() != null) { onNewline(out); print(out, "if ("); if (role.getMultiplicityUpper() == 1) { print(out, "has"); } else { print(out, "hasAny"); } print(out, capitalize(role.getName())); println(out, "()) handleAttemptToDeleteConnectedObject();"); } } endMethodBody(out); } // ----------------------------------------------------------------------------------- // code related to the database reading/writing protected void generateDatabaseReader(DomainClass domClass, PrintWriter out) { newline(out); printMethod(out, "protected", "void", "readSlotsFromResultSet", makeArg("java.sql.ResultSet", "rs"), makeArg("int", "txNumber")); print(out, " throws java.sql.SQLException"); startMethodBody(out); if (domClass.hasSuperclass()) { println(out, "super.readSlotsFromResultSet(rs, txNumber);"); } for (Slot slot : domClass.getSlotsList()) { generateOneSlotRsReader(out, slot.getName(), slot.getSlotType()); } for (Role role : domClass.getRoleSlotsList()) { if ((role.getName() != null) && (role.getMultiplicityUpper() == 1)) { generateOneRoleSlotRsReader(out, role.getName()); } } endMethodBody(out); } protected void generateOneSlotRsReader(PrintWriter out, String name, ValueType type) { onNewline(out); print(out, "set$"); print(out, name); print(out, "("); printRsReaderExpressions(out, type, DbUtil.convertToDBStyle(name), 0); print(out, ", txNumber);"); } protected int printRsReaderExpressions(PrintWriter out, ValueType vt, String colBaseName, int colNum) { if (vt.isBuiltin()) { printBuiltinReadExpression(out, vt, colBaseName, colNum); return colNum + 1; } for (ExternalizationElement extElem : vt.getExternalizationElements()) { colNum = printRsReaderExpressions(out, extElem.getType(), colBaseName, colNum); } return colNum; } protected void generateOneRoleSlotRsReader(PrintWriter out, String name) { onNewline(out); print(out, "this."); print(out, name); print(out, ".persistentLoad("); print(out, RESULT_SET_READER_CLASS); print(out, ".readDomainObject(rs, \"OID_"); print(out, DbUtil.convertToDBStyle(name)); print(out, "\"), txNumber);"); } protected void printBuiltinReadExpression(PrintWriter out, ValueType vt, String colBaseName, int colNum) { print(out, RESULT_SET_READER_CLASS); print(out, ".read"); if (vt.isEnum()) { print(out, "Enum("); print(out, vt.getFullname()); print(out, ".class, "); } else { print(out, vt.getDomainName()); print(out, "("); } print(out, "rs, \""); print(out, colBaseName); if (colNum > 0) { print(out, "__" + colNum); } print(out, "\")"); } protected void generateSlotDeclaration(PrintWriter out, String type, String name) { printWords(out, "private", type, name); println(out, ";"); } }