Skip to content

Commit f71a044

Browse files
emmanueltouzeryvojtechhabarta
authored andcommitted
fixes #86 - new emitter extension to generate bean property paths. (#87)
* fixes #86 - new emitter extension to generate bean property paths. * have the generated Field spec classes replicate the inheritance hierarchy instead of duplicating the fields, good advice @vojtechhabarta * respect the exportKeyword setting. * oops that was wrong
1 parent 71f673a commit f71a044

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cz.habarta.typescript.generator.ext;
2+
3+
import java.util.Arrays;
4+
import cz.habarta.typescript.generator.TypeProcessor;
5+
import cz.habarta.typescript.generator.DefaultTypeProcessor;
6+
import cz.habarta.typescript.generator.Settings;
7+
import cz.habarta.typescript.generator.compiler.ModelCompiler;
8+
import cz.habarta.typescript.generator.emitter.EmitterExtension;
9+
import cz.habarta.typescript.generator.emitter.TsModel;
10+
import cz.habarta.typescript.generator.ext.BeanPropertyPathExtension;
11+
import cz.habarta.typescript.generator.parser.Jackson2Parser;
12+
import cz.habarta.typescript.generator.parser.Model;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import org.junit.Assert;
16+
import org.junit.Test;
17+
18+
public class BeanPropertyPathExtensionTest {
19+
20+
static class ClassA {
21+
public String field1;
22+
public ClassB field2;
23+
}
24+
25+
static class ClassB {
26+
public int field1;
27+
}
28+
29+
@Test
30+
public void basicTest() {
31+
final StringBuilder data = new StringBuilder();
32+
final EmitterExtension.Writer writer = new EmitterExtension.Writer() {
33+
@Override
34+
public void writeIndentedLine(String line) {
35+
data.append(line + "\n");
36+
}
37+
};
38+
final Settings settings = new Settings();
39+
settings.sortDeclarations = true;
40+
final TypeProcessor typeProcessor = new DefaultTypeProcessor();
41+
final Model model = new Jackson2Parser(settings, typeProcessor).parseModel(ClassA.class);
42+
final TsModel tsModel = new ModelCompiler(settings, typeProcessor).javaToTypeScript(model);
43+
new BeanPropertyPathExtension().emitElements(writer, settings, false, tsModel);
44+
String dataStr = data.toString();
45+
Assert.assertEquals(29, dataStr.split("\n").length);
46+
Assert.assertTrue(dataStr.contains("class ClassAFields"));
47+
Assert.assertTrue(dataStr.contains("class ClassBFields"));
48+
}
49+
}

0 commit comments

Comments
 (0)