|
| 1 | +package cz.habarta.typescript.generator.ext; |
| 2 | + |
| 3 | +import java.util.*; |
| 4 | +import cz.habarta.typescript.generator.Settings; |
| 5 | +import cz.habarta.typescript.generator.TsType; |
| 6 | +import cz.habarta.typescript.generator.TsType.GenericReferenceType; |
| 7 | +import cz.habarta.typescript.generator.compiler.EnumKind; |
| 8 | +import cz.habarta.typescript.generator.compiler.EnumMemberModel; |
| 9 | +import cz.habarta.typescript.generator.emitter.EmitterExtension; |
| 10 | +import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures; |
| 11 | +import cz.habarta.typescript.generator.emitter.TsBeanModel; |
| 12 | +import cz.habarta.typescript.generator.emitter.TsEnumModel; |
| 13 | +import cz.habarta.typescript.generator.emitter.TsModel; |
| 14 | +import cz.habarta.typescript.generator.emitter.TsPropertyModel; |
| 15 | + |
| 16 | +/** |
| 17 | + * Emitter which generates type-safe property path getters. |
| 18 | + * |
| 19 | + * Many javascript frameworks require that you specify "property paths" |
| 20 | + * to extract data from objects. For instance, if you have a data model |
| 21 | + * which is an array of items, and you want to display them in a grid, |
| 22 | + * you can give column specifications as strings, like "field1.field2". |
| 23 | + * With this emitter you can specify such paths like so: |
| 24 | + * {@code ClassName.field1.field2.get()} |
| 25 | + * Once you call {@code get()}, you get a string |
| 26 | + * (in this case "field1.field2") |
| 27 | + */ |
| 28 | +public class BeanPropertyPathExtension extends EmitterExtension { |
| 29 | + |
| 30 | + @Override |
| 31 | + public EmitterExtensionFeatures getFeatures() { |
| 32 | + final EmitterExtensionFeatures features = new EmitterExtensionFeatures(); |
| 33 | + features.generatesRuntimeCode = true; |
| 34 | + return features; |
| 35 | + } |
| 36 | + |
| 37 | + @Override |
| 38 | + public void emitElements(Writer writer, Settings settings, boolean exportKeyword, TsModel model) { |
| 39 | + emitFieldsClass(writer, settings); |
| 40 | + |
| 41 | + Set<TsBeanModel> emittedBeans = new HashSet<>(); |
| 42 | + for (TsBeanModel bean : model.getBeans()) { |
| 43 | + emittedBeans.addAll( |
| 44 | + writeBeanAndParentsFieldSpecs(writer, settings, model, emittedBeans, bean)); |
| 45 | + } |
| 46 | + for (TsBeanModel bean : model.getBeans()) { |
| 47 | + createBeanFieldConstant(writer, exportKeyword, bean); |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + private static void emitFieldsClass(Writer writer, Settings settings) { |
| 52 | + List<String> fieldsClassLines = Arrays.asList( |
| 53 | + "class Fields {", |
| 54 | + " protected parent: Fields | undefined;", |
| 55 | + " protected name: string | undefined;", |
| 56 | + " constructor(parent?: Fields, name?: string) {", |
| 57 | + " this.parent = parent;", |
| 58 | + " this.name = name;", |
| 59 | + " };", |
| 60 | + " get(): string | undefined {", |
| 61 | + " if (this.parent && this.parent.get()) {", |
| 62 | + " return this.name ? this.parent.get() + \".\" + this.name : this.parent.get();", |
| 63 | + " } else {", |
| 64 | + " return this.name;", |
| 65 | + " }", |
| 66 | + " }", |
| 67 | + "}"); |
| 68 | + writer.writeIndentedLine(""); |
| 69 | + for (String fieldsClassLine : fieldsClassLines) { |
| 70 | + writer.writeIndentedLine(fieldsClassLine.replace(" ", settings.indentString)); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + /** |
| 75 | + * Emits a bean and its parent beans before if needed. |
| 76 | + * Returns the list of beans that were emitted. |
| 77 | + */ |
| 78 | + private static Set<TsBeanModel> writeBeanAndParentsFieldSpecs( |
| 79 | + Writer writer, Settings settings, TsModel model, Set<TsBeanModel> emittedSoFar, TsBeanModel bean) { |
| 80 | + if (emittedSoFar.contains(bean)) { |
| 81 | + return new HashSet<>(); |
| 82 | + } |
| 83 | + final TsBeanModel parentBean = getBeanModelByType(model, bean.getParent()); |
| 84 | + final Set<TsBeanModel> emittedBeans = parentBean != null |
| 85 | + ? writeBeanAndParentsFieldSpecs(writer, settings, model, emittedSoFar, parentBean) |
| 86 | + : new HashSet<TsBeanModel>(); |
| 87 | + final String parentClassName = parentBean != null |
| 88 | + ? getBeanModelClassName(parentBean) + "Fields" |
| 89 | + : "Fields"; |
| 90 | + writer.writeIndentedLine(""); |
| 91 | + writer.writeIndentedLine( |
| 92 | + "class " + getBeanModelClassName(bean) + "Fields extends " + parentClassName + " {"); |
| 93 | + writer.writeIndentedLine( |
| 94 | + settings.indentString + "constructor(parent?: Fields, name?: string) { super(parent, name); }"); |
| 95 | + for (TsPropertyModel property : bean.getProperties()) { |
| 96 | + writeBeanProperty(writer, settings, model, bean, property); |
| 97 | + } |
| 98 | + writer.writeIndentedLine("}"); |
| 99 | + |
| 100 | + emittedBeans.add(bean); |
| 101 | + return emittedBeans; |
| 102 | + } |
| 103 | + |
| 104 | + private static TsBeanModel getBeanModelByType(TsModel model, TsType type) { |
| 105 | + for (TsBeanModel curBean : model.getBeans()) { |
| 106 | + if (curBean.getName().equals(type)) { |
| 107 | + return curBean; |
| 108 | + } |
| 109 | + } |
| 110 | + return null; |
| 111 | + } |
| 112 | + |
| 113 | + /** |
| 114 | + * return a class name formatted for rendering in code |
| 115 | + * as part of another class name (so, for generics, strip |
| 116 | + * the type arguments) |
| 117 | + */ |
| 118 | + private static String getBeanModelClassName(TsBeanModel bean) { |
| 119 | + return bean.getName() instanceof GenericReferenceType |
| 120 | + ? ((GenericReferenceType)bean.getName()).symbol.toString() |
| 121 | + : bean.getName().toString(); |
| 122 | + } |
| 123 | + |
| 124 | + private static void writeBeanProperty( |
| 125 | + Writer writer, Settings settings, TsModel model, TsBeanModel bean, |
| 126 | + TsPropertyModel property) { |
| 127 | + TsBeanModel fieldBeanModel = getBeanModelByType(model, property.getTsType()); |
| 128 | + String fieldClassName = fieldBeanModel != null ? getBeanModelClassName(fieldBeanModel) : ""; |
| 129 | + writer.writeIndentedLine( |
| 130 | + settings.indentString + property.getName() + " = new " + fieldClassName + "Fields(this, \"" + property.getName() + "\");"); |
| 131 | + } |
| 132 | + |
| 133 | + private static void createBeanFieldConstant(Writer writer, boolean exportKeyword, TsBeanModel bean) { |
| 134 | + writer.writeIndentedLine((exportKeyword ? "export " : "") |
| 135 | + + "const " + getBeanModelClassName(bean) + " = new " + getBeanModelClassName(bean) + "Fields();"); |
| 136 | + } |
| 137 | +} |
0 commit comments