package org.andork.codegen.model; import static com.schwab.codegen.NameUtils.cap; import static com.schwab.codegen.NameUtils.constantify; import static com.schwab.codegen.NameUtils.getElementSingularName; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.schwab.att.core.model.gen.ModelTemplate; import com.schwab.att.core.type.CompactTypeFormatter; import com.schwab.att.core.type.ReflectionUtils; import com.schwab.att.core.type.TypeFormatter; import com.schwab.codegen.CodeBuilder; import com.schwab.codegen.Model; import com.schwab.codegen.CodeBuilder.Block; import com.schwab.codegen.builder.BuilderIgnore; public class ModelGenerator { public static void main(String[] args) { // System.out.println(new // ModelGenerator().generateModel(MultiLegChainResponse.class)); System.out.println(new ModelGenerator().generateModel(TestTemplate.class)); } private final Map<Class<?>, String> nameMap = new HashMap<Class<?>, String>(); private final Map<Type, String> fqNameMap = new HashMap<Type, String>(); private final TypeFormatter typeFormatter = new CompactTypeFormatter(); private String equalsMethod = "Java16PlusMethods.equals"; public Block generateModel(Class<?> templateType) { String typeDecl = Modifier.toString(templateType.getModifiers()); if (templateType.isInterface()) { // Modifier.toString() already added "interface" typeDecl += " "; } else { typeDecl += " class "; } typeDecl += nameMap(templateType); if (templateType.getSuperclass() != null && templateType.getSuperclass() != Object.class) { typeDecl += " extends " + nameMap(templateType.getSuperclass()); } Class<?>[] ifaces = templateType.getInterfaces(); if (!templateType.isInterface()) { ifaces = Arrays.copyOf(ifaces, ifaces.length + 1); ifaces[ifaces.length - 1] = Model.class; } if (ifaces.length > 0) { typeDecl += " implements " + nameMap(ifaces[0]); for (int i = 1; i < ifaces.length; i++) { typeDecl += ", " + nameMap(ifaces[i]); } } Block typeBlock = CodeBuilder.newJavaBlock(typeDecl); if (!templateType.isInterface()) { Block propertiesBlock = typeBlock.newJavaBlock("public static enum Properties"); StringBuffer lastLine = null; for (Field field : templateType.getDeclaredFields()) { if (ignoreField(field)) { continue; } StringBuffer line = propertiesBlock.addLine(constantify(field.getName())); if (lastLine != null) { lastLine.append(","); } lastLine = line; } if (lastLine != null) { lastLine.append(";"); } typeBlock.addLine(); for (Field field : templateType.getDeclaredFields()) { if (ignoreField(field)) { continue; } StringBuffer fieldLine = typeBlock.addLine(); fieldLine.append(Modifier.toString(field.getModifiers())); fieldLine.append(" ").append(fqNameMap(field.getGenericType())).append(" ").append(field.getName()).append(";"); } typeBlock.addLine(); typeBlock.addLine("@BuilderIgnore"); typeBlock.addLine("private HierarchicalBasicPropertyChangeSupport propertyChangeSupport = new HierarchicalBasicPropertyChangeSupport();"); typeBlock.addLine(); typeBlock.addLine("@BuilderIgnore"); typeBlock.addLine("private HierarchicalBasicPropertyChangeListener childChangeListener = new HierarchicalBasicPropertyChangePropagator(this, propertyChangeSupport);"); typeBlock.addLine(); typeBlock.addLine("@Override"); Block getPropChangeSupportBlock = typeBlock.newJavaBlock("public HierarchicalBasicPropertyChangeSupport getPropertyChangeSupport()"); getPropChangeSupportBlock.addLine("return propertyChangeSupport;"); } for (Field field : templateType.getDeclaredFields()) { if (ignoreField(field)) { continue; } StringBuffer getterLine = new StringBuffer(); StringBuffer setterLine = new StringBuffer(); String fieldTypeStr = fqNameMap(field.getGenericType()); String setterTypeStr = fieldTypeStr; if (Collection.class.isAssignableFrom(field.getType())) { setterTypeStr = fqNameMap(ReflectionUtils.getAddAllableType(field.getGenericType())); } getterLine.append("public ").append(fieldTypeStr).append(" get").append(cap(field.getName())).append("()"); setterLine.append("public void set").append(cap(field.getName())).append("(").append(setterTypeStr).append(" ").append(field.getName()).append(")"); typeBlock.addLine(); Block getterBlock = typeBlock.newJavaBlock(getterLine.toString()); typeBlock.addLine(); Block setterBlock = typeBlock.newJavaBlock(setterLine.toString()); if (Collection.class.isAssignableFrom(field.getType())) { Type elemType = ReflectionUtils.getTypeParameter(field.getGenericType(), 0); if (elemType == null) { elemType = Object.class; } String elemTypeStr = fqNameMap(elemType); String elemNameSingular = getElementSingularName(field); String unmodTypeStr = ""; if (List.class.isAssignableFrom(field.getType())) { unmodTypeStr = "List"; } else if (Set.class.isAssignableFrom(field.getType())) { unmodTypeStr = "Set"; } getterBlock.addLine("return Collections.unmodifiable" + unmodTypeStr + "(").append(field.getName()).append(");"); if (isModelType(elemType)) { Block removeLoop = setterBlock.newJavaBlock("for (" + elemTypeStr + " a : this." + field.getName() + ")"); removeLoop.addLine("a.getPropertyChangeSupport().removePropertyChangeListener(childChangeListener);"); } else if (couldBeModelType(elemType)) { Block removeLoop = setterBlock.newJavaBlock("for (" + elemTypeStr + " a : this." + field.getName() + ")"); Block ifModelBlock = removeLoop.newJavaBlock("if (a instanceof Model)"); ifModelBlock.addLine("((Model) a).getPropertyChangeSupport().removePropertyChangeListener(childChangeListener);"); } setterBlock.addLine("this.").append(field.getName()).append(".clear();"); setterBlock.addLine("this.").append(field.getName()).append(".addAll(").append(field.getName()).append(");"); if (isModelType(elemType)) { Block addLoop = setterBlock.newJavaBlock("for (" + elemTypeStr + " a : " + field.getName() + ")"); addLoop.addLine("a.getPropertyChangeSupport().addPropertyChangeListener(childChangeListener);"); } else if (couldBeModelType(elemType)) { Block addLoop = setterBlock.newJavaBlock("for (" + elemTypeStr + " a : " + field.getName() + ")"); Block ifModelBlock = addLoop.newJavaBlock("if (a instanceof Model)"); ifModelBlock.addLine("((Model) a).getPropertyChangeSupport().addPropertyChangeListener(childChangeListener);"); } setterBlock.addLine("propertyChangeSupport.fireChildrenChanged(this);"); typeBlock.addLine(); Block addBlock = typeBlock.newJavaBlock("public void add" + cap(elemNameSingular) + "(" + elemTypeStr + " " + elemNameSingular + ")"); Block ifAddedBlock = addBlock.newJavaBlock("if (this." + field.getName() + ".add(" + elemNameSingular + "))"); if (isModelType(elemType)) { ifAddedBlock.addLine(elemNameSingular).append(".getPropertyChangeSupport().addPropertyChangeListener(childChangeListener);"); } else if (couldBeModelType(elemType)) { Block ifModelBlock = ifAddedBlock.newJavaBlock("if (" + elemNameSingular + " instanceof Model)"); ifModelBlock.addLine("((Model) ").append(elemNameSingular).append(").addPropertyChangeListener(childChangeListener);"); } ifAddedBlock.addLine("propertyChangeSupport.fireChildAdded(this, ").append(elemNameSingular).append(");"); typeBlock.addLine(); Block removeBlock = typeBlock.newJavaBlock("public void remove" + cap(elemNameSingular) + "(" + elemTypeStr + " " + elemNameSingular + ")"); Block ifRemovedBlock = removeBlock.newJavaBlock("if (this." + field.getName() + ".remove(" + elemNameSingular + "))"); if (isModelType(elemType)) { ifRemovedBlock.addLine(elemNameSingular).append(".getPropertyChangeSupport().removePropertyChangeListener(childChangeListener);"); } else if (couldBeModelType(elemType)) { Block ifModelBlock = ifRemovedBlock.newJavaBlock("if (" + elemNameSingular + " instanceof Model)"); ifModelBlock.addLine("((Model) ").append(elemNameSingular).append(").getPropertyChangeSupport().removePropertyChangeListener(childChangeListener);"); } ifRemovedBlock.addLine("propertyChangeSupport.fireChildRemoved(this, ").append(elemNameSingular).append(");"); } else if (field.getType().isArray()) { } else { getterBlock.addLine("return ").append(field.getName()).append(";"); StringBuffer ifStmt = new StringBuffer(); ifStmt.append("if (!").append(equalsMethod).append("(this.").append(field.getName()).append(", ").append(field.getName()).append("))"); Block equalsBlock = setterBlock.newJavaBlock(ifStmt.toString()); if (isModelType(field.getType())) { Block removeListenerBlock = equalsBlock.newJavaBlock("if (this." + field.getName() + " != null)"); removeListenerBlock.addLine("this.").append(field.getName()).append(".getPropertyChangeSupport().removePropertyChangeListener(childChangeListener);"); } equalsBlock.addLine(fieldTypeStr).append(" old = this.").append(field.getName()).append(";"); equalsBlock.addLine("this.").append(field.getName()).append(" = ").append(field.getName()).append(";"); if (isModelType(field.getType())) { Block addListenerBlock = equalsBlock.newJavaBlock("if (" + field.getName() + " != null)"); addListenerBlock.addLine(field.getName()).append(".getPropertyChangeSupport().addPropertyChangeListener(childChangeListener);"); } equalsBlock.addLine("propertyChangeSupport.firePropertyChange(this, Properties.").append(constantify(field.getName())).append(", old, ").append(field.getName()).append(");"); } } for (Class<?> enclosed : templateType.getDeclaredClasses()) { if (!isModelType(enclosed)) { continue; } typeBlock.addLine(); typeBlock.addBlock(generateModel(enclosed)); } return typeBlock; } protected boolean ignoreField(Field field) { return field.getAnnotation(BuilderIgnore.class) != null || Modifier.isStatic(field.getModifiers()); } protected boolean isModelType(Type type) { return ReflectionUtils.getRawType(type).getAnnotation(ModelTemplate.class) != null; } protected boolean couldBeModelType(Type type) { return !ReflectionUtils.getRawType(type).isEnum() && !ReflectionUtils.getRawType(type).getName().startsWith("java"); } public String nameMap(Class<?> type) { String name = nameMap.get(type); if (name == null) { name = type.getSimpleName(); nameMap.put(type, name); } return name; } public String fqNameMap(Type type) { String name = fqNameMap.get(type); if (name == null) { name = typeFormatter.format(type); fqNameMap.put(type, name); } return name; } }