Skip to content

Commit c270962

Browse files
Merge pull request #81 from vojtechhabarta/tagged-unions
Tagged unions
2 parents a564ef7 + 86fc365 commit c270962

File tree

22 files changed

+530
-65
lines changed

22 files changed

+530
-65
lines changed

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/CustomMappingTypeProcessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
package cz.habarta.typescript.generator;
33

4+
import cz.habarta.typescript.generator.util.Utils;
45
import java.lang.reflect.Type;
56
import java.util.*;
67

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/ExcludingTypeProcessor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
package cz.habarta.typescript.generator;
33

4+
import cz.habarta.typescript.generator.util.Utils;
45
import java.lang.reflect.Type;
56
import java.util.*;
67

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/GenericsTypeProcessor.java

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class GenericsTypeProcessor implements TypeProcessor {
1212
public TypeProcessor.Result processType(Type javaType, TypeProcessor.Context context) {
1313
if (javaType instanceof TypeVariable) {
1414
final TypeVariable<?> typeVariable = (TypeVariable) javaType;
15-
return new Result(new GenericVariableType(typeVariable.getName()));
15+
return new Result(new TsType.GenericVariableType(typeVariable.getName()));
1616
}
1717
if (javaType instanceof Class) {
1818
final Class<?> javaClass = (Class<?>) javaType;
@@ -44,33 +44,10 @@ private Result processGenericClass(Class<?> rawType, Type[] typeArguments, TypeP
4444
discoveredClasses.addAll(typeArgumentResult.getDiscoveredClasses());
4545
}
4646
// result
47-
final GenericReferenceType type = new GenericReferenceType(rawSymbol, tsTypeArguments);
47+
final TsType.GenericReferenceType type = new TsType.GenericReferenceType(rawSymbol, tsTypeArguments);
4848
return new Result(type, discoveredClasses);
4949
}
5050
return null;
5151
}
5252

53-
private static class GenericReferenceType extends TsType.ReferenceType {
54-
55-
public final List<TsType> typeArguments;
56-
57-
public GenericReferenceType(Symbol symbol, List<TsType> typeArguments) {
58-
super(symbol);
59-
this.typeArguments = typeArguments;
60-
}
61-
62-
@Override
63-
public String format(Settings settings) {
64-
return symbol + "<" + Utils.join(format(typeArguments, settings), ", ") + ">";
65-
}
66-
}
67-
68-
private static class GenericVariableType extends TsType.BasicType {
69-
70-
public GenericVariableType(String name) {
71-
super(name);
72-
}
73-
74-
}
75-
7653
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/JaxrsApplicationScanner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package cz.habarta.typescript.generator;
33

44
import cz.habarta.typescript.generator.parser.*;
5+
import cz.habarta.typescript.generator.util.Utils;
56
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
67
import java.lang.annotation.*;
78
import java.lang.reflect.*;

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ public class Settings {
3131
public List<String> referencedFiles = new ArrayList<>();
3232
public List<String> importDeclarations = new ArrayList<>();
3333
public Map<String, String> customTypeMappings = new LinkedHashMap<>();
34-
public DateMapping mapDate = DateMapping.asDate;
35-
public EnumMapping mapEnum = EnumMapping.asUnion;
34+
public DateMapping mapDate; // default is DateMapping.asDate
35+
public EnumMapping mapEnum; // default is EnumMapping.asUnion
36+
public boolean disableTaggedUnions = false;
3637
public TypeProcessor customTypeProcessor = null;
3738
public boolean sortDeclarations = false;
3839
public boolean sortTypeDeclarations = false;

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/TsType.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package cz.habarta.typescript.generator;
33

44
import cz.habarta.typescript.generator.compiler.Symbol;
5+
import cz.habarta.typescript.generator.util.Utils;
56
import java.util.*;
67

78

@@ -79,6 +80,29 @@ public String format(Settings settings) {
7980

8081
}
8182

83+
public static class GenericReferenceType extends TsType.ReferenceType {
84+
85+
public final List<TsType> typeArguments;
86+
87+
public GenericReferenceType(Symbol symbol, List<TsType> typeArguments) {
88+
super(symbol);
89+
this.typeArguments = typeArguments;
90+
}
91+
92+
@Override
93+
public String format(Settings settings) {
94+
return symbol + "<" + Utils.join(format(typeArguments, settings), ", ") + ">";
95+
}
96+
}
97+
98+
public static class GenericVariableType extends TsType.BasicType {
99+
100+
public GenericVariableType(String name) {
101+
super(name);
102+
}
103+
104+
}
105+
82106
public static class EnumReferenceType extends ReferenceType {
83107
public EnumReferenceType(Symbol symbol) {
84108
super(symbol);

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,37 @@ public ModelCompiler(Settings settings, TypeProcessor typeProcessor) {
4141
public TsModel javaToTypeScript(Model model) {
4242
final SymbolTable symbolTable = new SymbolTable(settings);
4343
TsModel tsModel = processModel(symbolTable, model);
44+
45+
// dates
4446
tsModel = transformDates(symbolTable, tsModel);
45-
if (settings.mapEnum == EnumMapping.asUnion || settings.mapEnum == EnumMapping.asInlineUnion) {
47+
48+
// enums
49+
if (settings.mapEnum == null || settings.mapEnum == EnumMapping.asUnion || settings.mapEnum == EnumMapping.asInlineUnion) {
4650
tsModel = transformEnumsToUnions(tsModel);
4751
}
4852
if (settings.mapEnum == EnumMapping.asInlineUnion) {
4953
tsModel = inlineEnums(tsModel, symbolTable);
5054
}
51-
symbolTable.resolveSymbolNames(settings);
55+
56+
// tagged unions
57+
tsModel = createAndUseTaggedUnions(symbolTable, tsModel);
58+
59+
symbolTable.resolveSymbolNames();
5260
return tsModel;
5361
}
5462

5563
public TsType javaToTypeScript(Type type) {
56-
final BeanModel beanModel = new BeanModel(Object.class, Object.class, Collections.<Type>emptyList(), Collections.singletonList(new PropertyModel("property", type, false, null, null)));
64+
final BeanModel beanModel = new BeanModel(Object.class, Object.class, null, null, null, Collections.<Type>emptyList(), Collections.singletonList(new PropertyModel("property", type, false, null, null)), null);
5765
final Model model = new Model(Collections.singletonList(beanModel), Collections.<EnumModel<?>>emptyList());
5866
final TsModel tsModel = javaToTypeScript(model);
5967
return tsModel.getBeans().get(0).getProperties().get(0).getTsType();
6068
}
6169

6270
private TsModel processModel(SymbolTable symbolTable, Model model) {
71+
final Map<Type, List<BeanModel>> children = createChildrenMap(model);
6372
final List<TsBeanModel> beans = new ArrayList<>();
6473
for (BeanModel bean : model.getBeans()) {
65-
beans.add(processBean(symbolTable, bean));
74+
beans.add(processBean(symbolTable, children, bean));
6675
}
6776
final List<TsEnumModel<?>> enums = new ArrayList<>();
6877
for (EnumModel<?> enumModel : model.getEnums()) {
@@ -72,7 +81,21 @@ private TsModel processModel(SymbolTable symbolTable, Model model) {
7281
return new TsModel(beans, enums, typeAliases);
7382
}
7483

75-
private TsBeanModel processBean(SymbolTable symbolTable, BeanModel bean) {
84+
private Map<Type, List<BeanModel>> createChildrenMap(Model model) {
85+
final Map<Type, List<BeanModel>> children = new LinkedHashMap<>();
86+
for (BeanModel bean : model.getBeans()) {
87+
final Type parent = bean.getParent();
88+
if (parent != null) {
89+
if (!children.containsKey(parent)) {
90+
children.put(parent, new ArrayList<BeanModel>());
91+
}
92+
children.get(parent).add(bean);
93+
}
94+
}
95+
return children;
96+
}
97+
98+
private TsBeanModel processBean(SymbolTable symbolTable, Map<Type, List<BeanModel>> children, BeanModel bean) {
7699
final TsType beanType = typeFromJava(symbolTable, bean.getOrigin());
77100
TsType parentType = typeFromJava(symbolTable, bean.getParent());
78101
if (parentType != null && parentType.equals(TsType.Any)) {
@@ -89,7 +112,43 @@ private TsBeanModel processBean(SymbolTable symbolTable, BeanModel bean) {
89112
for (PropertyModel property : bean.getProperties()) {
90113
properties.add(processProperty(symbolTable, bean, property));
91114
}
92-
return new TsBeanModel(bean.getOrigin(), beanType, parentType, interfaces, properties, bean.getComments());
115+
116+
if (bean.getDiscriminantProperty() != null && !containsProperty(properties, bean.getDiscriminantProperty())) {
117+
final List<BeanModel> selfAndDescendants = getSelfAndDescendants(bean, children);
118+
final List<TsType.StringLiteralType> literals = new ArrayList<>();
119+
for (BeanModel descendant : selfAndDescendants) {
120+
if (descendant.getDiscriminantLiteral() != null) {
121+
literals.add(new TsType.StringLiteralType(descendant.getDiscriminantLiteral()));
122+
}
123+
}
124+
final TsType discriminantType = literals.isEmpty()
125+
? TsType.String
126+
: new TsType.UnionType(literals);
127+
properties.add(0, new TsPropertyModel(bean.getDiscriminantProperty(), discriminantType, null));
128+
}
129+
130+
return new TsBeanModel(bean.getOrigin(), beanType, parentType, bean.getTaggedUnionClasses(), interfaces, properties, bean.getComments());
131+
}
132+
133+
private static List<BeanModel> getSelfAndDescendants(BeanModel bean, Map<Type, List<BeanModel>> children) {
134+
final List<BeanModel> descendants = new ArrayList<>();
135+
descendants.add(bean);
136+
final List<BeanModel> directDescendants = children.get(bean.getOrigin());
137+
if (directDescendants != null) {
138+
for (BeanModel descendant : directDescendants) {
139+
descendants.addAll(getSelfAndDescendants(descendant, children));
140+
}
141+
}
142+
return descendants;
143+
}
144+
145+
private static boolean containsProperty(List<TsPropertyModel> properties, String propertyName) {
146+
for (TsPropertyModel property : properties) {
147+
if (property.getName().equals(propertyName)) {
148+
return true;
149+
}
150+
}
151+
return false;
93152
}
94153

95154
private TsPropertyModel processProperty(SymbolTable symbolTable, BeanModel bean, PropertyModel property) {
@@ -187,6 +246,46 @@ public TsType transform(TsType tsType) {
187246
return newTsModel.setTypeAliases(aliases);
188247
}
189248

249+
private TsModel createAndUseTaggedUnions(final SymbolTable symbolTable, TsModel tsModel) {
250+
if (settings.disableTaggedUnions) {
251+
return tsModel;
252+
}
253+
// create tagged unions
254+
final LinkedHashSet<TsAliasModel> typeAliases = new LinkedHashSet<>(tsModel.getTypeAliases());
255+
for (TsBeanModel bean : tsModel.getBeans()) {
256+
if (bean.getTaggedUnionClasses() != null) {
257+
if (bean.getName() instanceof TsType.ReferenceType) {
258+
final TsType.ReferenceType unionName = new TsType.ReferenceType(symbolTable.getSymbol(bean.getOrigin(), "Union"));
259+
final List<TsType> unionTypes = new ArrayList<>();
260+
for (Class<?> cls : bean.getTaggedUnionClasses()) {
261+
final TsType type = new TsType.ReferenceType(symbolTable.getSymbol(cls));
262+
unionTypes.add(type);
263+
}
264+
final TsType.UnionType union = new TsType.UnionType(unionTypes);
265+
typeAliases.add(new TsAliasModel(bean.getOrigin(), unionName, union, null));
266+
}
267+
}
268+
}
269+
// use tagged unions
270+
final TsModel model = transformBeanPropertyTypes(tsModel, new TsType.Transformer() {
271+
@Override
272+
public TsType transform(TsType tsType) {
273+
if (tsType instanceof TsType.ReferenceType) {
274+
final TsType.ReferenceType referenceType = (TsType.ReferenceType) tsType;
275+
if (!(referenceType instanceof TsType.GenericReferenceType)) {
276+
final Class<?> cls = symbolTable.getSymbolClass(referenceType.symbol);
277+
final Symbol unionSymbol = symbolTable.hasSymbol(cls, "Union");
278+
if (unionSymbol != null) {
279+
return new TsType.ReferenceType(unionSymbol);
280+
}
281+
}
282+
}
283+
return tsType;
284+
}
285+
});
286+
return model.setTypeAliases(new ArrayList<>(typeAliases));
287+
}
288+
190289
private static TsModel transformBeanPropertyTypes(TsModel tsModel, TsType.Transformer transformer) {
191290
final List<TsBeanModel> newBeans = new ArrayList<>();
192291
for (TsBeanModel bean : tsModel.getBeans()) {
@@ -195,7 +294,7 @@ private static TsModel transformBeanPropertyTypes(TsModel tsModel, TsType.Transf
195294
final TsType newType = TsType.transformTsType(property.getTsType(), transformer);
196295
newProperties.add(property.setTsType(newType));
197296
}
198-
newBeans.add(bean.setProperties(newProperties));
297+
newBeans.add(bean.withProperties(newProperties));
199298
}
200299
return tsModel.setBeans(newBeans);
201300
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/SymbolTable.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,44 @@
22
package cz.habarta.typescript.generator.compiler;
33

44
import cz.habarta.typescript.generator.Settings;
5+
import cz.habarta.typescript.generator.util.Pair;
56
import java.util.*;
67

78

9+
/**
10+
* Name table.
11+
*/
812
public class SymbolTable {
913

1014
private final Settings settings;
11-
private final LinkedHashMap<Class<?>, Symbol> symbols = new LinkedHashMap<>();
15+
private final LinkedHashMap<Pair<Class<?>, String>, Symbol> symbols = new LinkedHashMap<>();
1216
private final LinkedHashMap<String, Symbol> syntheticSymbols = new LinkedHashMap<>();
1317

1418
public SymbolTable(Settings settings) {
1519
this.settings = settings;
1620
}
1721

1822
public Symbol getSymbol(Class<?> cls) {
19-
if (!symbols.containsKey(cls)) {
20-
symbols.put(cls, new Symbol("$" + cls.getName().replace('.', '$') + "$"));
23+
return getSymbol(cls, null);
24+
}
25+
26+
public Symbol getSymbol(Class<?> cls, String suffix) {
27+
final Pair<Class<?>, String> key = Pair.<Class<?>, String>of(cls, suffix);
28+
if (!symbols.containsKey(key)) {
29+
final String suffixString = suffix != null ? suffix : "";
30+
symbols.put(key, new Symbol("$" + cls.getName().replace('.', '$') + suffixString + "$"));
2131
}
22-
return symbols.get(cls);
32+
return symbols.get(key);
33+
}
34+
35+
public Symbol hasSymbol(Class<?> cls, String suffix) {
36+
return symbols.get(Pair.<Class<?>, String>of(cls, suffix));
2337
}
2438

2539
public Class<?> getSymbolClass(Symbol symbol) {
26-
for (Map.Entry<Class<?>, Symbol> entry : symbols.entrySet()) {
40+
for (Map.Entry<Pair<Class<?>, String>, Symbol> entry : symbols.entrySet()) {
2741
if (entry.getValue() == symbol) {
28-
return entry.getKey();
42+
return entry.getKey().getValue1();
2943
}
3044
}
3145
return null;
@@ -38,12 +52,14 @@ public Symbol getSyntheticSymbol(String name) {
3852
return syntheticSymbols.get(name);
3953
}
4054

41-
public void resolveSymbolNames(Settings settings) {
55+
public void resolveSymbolNames() {
4256
final Map<String, List<Class<?>>> names = new LinkedHashMap<>();
43-
for (Map.Entry<Class<?>, Symbol> entry : symbols.entrySet()) {
44-
final Class<?> cls = entry.getKey();
57+
for (Map.Entry<Pair<Class<?>, String>, Symbol> entry : symbols.entrySet()) {
58+
final Class<?> cls = entry.getKey().getValue1();
59+
final String suffix = entry.getKey().getValue2();
4560
final Symbol symbol = entry.getValue();
46-
final String name = getMappedName(cls);
61+
final String suffixString = suffix != null ? suffix : "";
62+
final String name = getMappedName(cls) + suffixString;
4763
symbol.name = name;
4864
if (!names.containsKey(name)) {
4965
names.put(name, new ArrayList<Class<?>>());

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/Emitter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import cz.habarta.typescript.generator.*;
55
import cz.habarta.typescript.generator.compiler.EnumKind;
66
import cz.habarta.typescript.generator.compiler.EnumMemberModel;
7+
import cz.habarta.typescript.generator.util.Utils;
78
import java.io.*;
89
import java.text.*;
910
import java.util.*;

0 commit comments

Comments
 (0)