-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* 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
- Loading branch information
1 parent
71f673a
commit f71a044
Showing
2 changed files
with
186 additions
and
0 deletions.
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
...tor-core/src/main/java/cz/habarta/typescript/generator/ext/BeanPropertyPathExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package cz.habarta.typescript.generator.ext; | ||
|
||
import java.util.*; | ||
import cz.habarta.typescript.generator.Settings; | ||
import cz.habarta.typescript.generator.TsType; | ||
import cz.habarta.typescript.generator.TsType.GenericReferenceType; | ||
import cz.habarta.typescript.generator.compiler.EnumKind; | ||
import cz.habarta.typescript.generator.compiler.EnumMemberModel; | ||
import cz.habarta.typescript.generator.emitter.EmitterExtension; | ||
import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures; | ||
import cz.habarta.typescript.generator.emitter.TsBeanModel; | ||
import cz.habarta.typescript.generator.emitter.TsEnumModel; | ||
import cz.habarta.typescript.generator.emitter.TsModel; | ||
import cz.habarta.typescript.generator.emitter.TsPropertyModel; | ||
|
||
/** | ||
* Emitter which generates type-safe property path getters. | ||
* | ||
* Many javascript frameworks require that you specify "property paths" | ||
* to extract data from objects. For instance, if you have a data model | ||
* which is an array of items, and you want to display them in a grid, | ||
* you can give column specifications as strings, like "field1.field2". | ||
* With this emitter you can specify such paths like so: | ||
* {@code ClassName.field1.field2.get()} | ||
* Once you call {@code get()}, you get a string | ||
* (in this case "field1.field2") | ||
*/ | ||
public class BeanPropertyPathExtension extends EmitterExtension { | ||
|
||
@Override | ||
public EmitterExtensionFeatures getFeatures() { | ||
final EmitterExtensionFeatures features = new EmitterExtensionFeatures(); | ||
features.generatesRuntimeCode = true; | ||
return features; | ||
} | ||
|
||
@Override | ||
public void emitElements(Writer writer, Settings settings, boolean exportKeyword, TsModel model) { | ||
emitFieldsClass(writer, settings); | ||
|
||
Set<TsBeanModel> emittedBeans = new HashSet<>(); | ||
for (TsBeanModel bean : model.getBeans()) { | ||
emittedBeans.addAll( | ||
writeBeanAndParentsFieldSpecs(writer, settings, model, emittedBeans, bean)); | ||
} | ||
for (TsBeanModel bean : model.getBeans()) { | ||
createBeanFieldConstant(writer, exportKeyword, bean); | ||
} | ||
} | ||
|
||
private static void emitFieldsClass(Writer writer, Settings settings) { | ||
List<String> fieldsClassLines = Arrays.asList( | ||
"class Fields {", | ||
" protected parent: Fields | undefined;", | ||
" protected name: string | undefined;", | ||
" constructor(parent?: Fields, name?: string) {", | ||
" this.parent = parent;", | ||
" this.name = name;", | ||
" };", | ||
" get(): string | undefined {", | ||
" if (this.parent && this.parent.get()) {", | ||
" return this.name ? this.parent.get() + \".\" + this.name : this.parent.get();", | ||
" } else {", | ||
" return this.name;", | ||
" }", | ||
" }", | ||
"}"); | ||
writer.writeIndentedLine(""); | ||
for (String fieldsClassLine : fieldsClassLines) { | ||
writer.writeIndentedLine(fieldsClassLine.replace(" ", settings.indentString)); | ||
} | ||
} | ||
|
||
/** | ||
* Emits a bean and its parent beans before if needed. | ||
* Returns the list of beans that were emitted. | ||
*/ | ||
private static Set<TsBeanModel> writeBeanAndParentsFieldSpecs( | ||
Writer writer, Settings settings, TsModel model, Set<TsBeanModel> emittedSoFar, TsBeanModel bean) { | ||
if (emittedSoFar.contains(bean)) { | ||
return new HashSet<>(); | ||
} | ||
final TsBeanModel parentBean = getBeanModelByType(model, bean.getParent()); | ||
final Set<TsBeanModel> emittedBeans = parentBean != null | ||
? writeBeanAndParentsFieldSpecs(writer, settings, model, emittedSoFar, parentBean) | ||
: new HashSet<TsBeanModel>(); | ||
final String parentClassName = parentBean != null | ||
? getBeanModelClassName(parentBean) + "Fields" | ||
: "Fields"; | ||
writer.writeIndentedLine(""); | ||
writer.writeIndentedLine( | ||
"class " + getBeanModelClassName(bean) + "Fields extends " + parentClassName + " {"); | ||
writer.writeIndentedLine( | ||
settings.indentString + "constructor(parent?: Fields, name?: string) { super(parent, name); }"); | ||
for (TsPropertyModel property : bean.getProperties()) { | ||
writeBeanProperty(writer, settings, model, bean, property); | ||
} | ||
writer.writeIndentedLine("}"); | ||
|
||
emittedBeans.add(bean); | ||
return emittedBeans; | ||
} | ||
|
||
private static TsBeanModel getBeanModelByType(TsModel model, TsType type) { | ||
for (TsBeanModel curBean : model.getBeans()) { | ||
if (curBean.getName().equals(type)) { | ||
return curBean; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* return a class name formatted for rendering in code | ||
* as part of another class name (so, for generics, strip | ||
* the type arguments) | ||
*/ | ||
private static String getBeanModelClassName(TsBeanModel bean) { | ||
return bean.getName() instanceof GenericReferenceType | ||
? ((GenericReferenceType)bean.getName()).symbol.toString() | ||
: bean.getName().toString(); | ||
} | ||
|
||
private static void writeBeanProperty( | ||
Writer writer, Settings settings, TsModel model, TsBeanModel bean, | ||
TsPropertyModel property) { | ||
TsBeanModel fieldBeanModel = getBeanModelByType(model, property.getTsType()); | ||
String fieldClassName = fieldBeanModel != null ? getBeanModelClassName(fieldBeanModel) : ""; | ||
writer.writeIndentedLine( | ||
settings.indentString + property.getName() + " = new " + fieldClassName + "Fields(this, \"" + property.getName() + "\");"); | ||
} | ||
|
||
private static void createBeanFieldConstant(Writer writer, boolean exportKeyword, TsBeanModel bean) { | ||
writer.writeIndentedLine((exportKeyword ? "export " : "") | ||
+ "const " + getBeanModelClassName(bean) + " = new " + getBeanModelClassName(bean) + "Fields();"); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
...core/src/test/java/cz/habarta/typescript/generator/ext/BeanPropertyPathExtensionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package cz.habarta.typescript.generator.ext; | ||
|
||
import java.util.Arrays; | ||
import cz.habarta.typescript.generator.TypeProcessor; | ||
import cz.habarta.typescript.generator.DefaultTypeProcessor; | ||
import cz.habarta.typescript.generator.Settings; | ||
import cz.habarta.typescript.generator.compiler.ModelCompiler; | ||
import cz.habarta.typescript.generator.emitter.EmitterExtension; | ||
import cz.habarta.typescript.generator.emitter.TsModel; | ||
import cz.habarta.typescript.generator.ext.BeanPropertyPathExtension; | ||
import cz.habarta.typescript.generator.parser.Jackson2Parser; | ||
import cz.habarta.typescript.generator.parser.Model; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
public class BeanPropertyPathExtensionTest { | ||
|
||
static class ClassA { | ||
public String field1; | ||
public ClassB field2; | ||
} | ||
|
||
static class ClassB { | ||
public int field1; | ||
} | ||
|
||
@Test | ||
public void basicTest() { | ||
final StringBuilder data = new StringBuilder(); | ||
final EmitterExtension.Writer writer = new EmitterExtension.Writer() { | ||
@Override | ||
public void writeIndentedLine(String line) { | ||
data.append(line + "\n"); | ||
} | ||
}; | ||
final Settings settings = new Settings(); | ||
settings.sortDeclarations = true; | ||
final TypeProcessor typeProcessor = new DefaultTypeProcessor(); | ||
final Model model = new Jackson2Parser(settings, typeProcessor).parseModel(ClassA.class); | ||
final TsModel tsModel = new ModelCompiler(settings, typeProcessor).javaToTypeScript(model); | ||
new BeanPropertyPathExtension().emitElements(writer, settings, false, tsModel); | ||
String dataStr = data.toString(); | ||
Assert.assertEquals(29, dataStr.split("\n").length); | ||
Assert.assertTrue(dataStr.contains("class ClassAFields")); | ||
Assert.assertTrue(dataStr.contains("class ClassBFields")); | ||
} | ||
} |