Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate C# domain events #190

Merged
merged 13 commits into from
Jun 4, 2021
12 changes: 6 additions & 6 deletions src/main/frontend/src/components/VersionContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
let active;
let showCodeModal = false;
let chosenLang;
let langs = [
'Java',
'CSharp',
]
let langs = Object.entries({
java: 'Java',
csharp: 'C#',
})
let sourceCode = "";
let showPreviewModal = false;

Expand Down Expand Up @@ -158,8 +158,8 @@
<CardTitle>Choose language to generate:</CardTitle>
<CardText>
<div class="mx-3">
{#each langs as lang}
<Radio bind:group={chosenLang} value={lang} color="primary">{lang}</Radio>
{#each langs as [lang, langName]}
<Radio bind:group={chosenLang} value={lang} color="primary">{langName}</Radio>
{/each}
{#if sourceCode}
<pre class="mt-3"><code>{sourceCode}</code></pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static TypeDefinitionCompiler compilerFor(final Stage stage, final String
*/
public static TypeDefinitionCompiler newCompilerFor(final Stage stage, final String language) {
switch (language) {
case "csharp":
case "java":
return forBackend(stage, new XoomCodeGenBackend(new SchemaTypeTemplateProcessingStep(), language));
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import io.vlingo.xoom.codegen.template.TemplateData;
import io.vlingo.xoom.codegen.template.TemplateStandard;
import io.vlingo.xoom.schemata.Schemata;
import io.vlingo.xoom.schemata.codegen.ast.types.TypeDefinition;
import io.vlingo.xoom.schemata.codegen.template.schematype.csharp.CSharpSchemaTypeTemplateData;
import io.vlingo.xoom.schemata.codegen.template.schematype.java.JavaSchemaTypeTemplateData;
import io.vlingo.xoom.schemata.codegen.template.SchemataTemplateStandard;

Expand All @@ -19,6 +21,8 @@ private static TemplateData create(final String language, final TypeDefinition t
switch (language) {
case "java":
return JavaSchemaTypeTemplateData.from(type, version);
case "csharp":
return CSharpSchemaTypeTemplateData.from(type, version);
default:
throw new IllegalArgumentException("Unsupported language: " + language);
}
Expand All @@ -28,4 +32,19 @@ private static TemplateData create(final String language, final TypeDefinition t
public TemplateStandard standard() {
return SchemataTemplateStandard.SCHEMA_TYPE;
}

protected List<String> packageSegments(String reference, String category) {
final String[] referenceParts = reference.split(Schemata.ReferenceSeparator);
if (referenceParts.length < Schemata.MinReferenceParts) {
throw new IllegalArgumentException("Invalid fully qualified type name. Valid type names look like this <organization>:<unit>:<context namespace>:<type name>[:<version>].");
}
final String namespace = referenceParts[2];
final String className = referenceParts[3];

final String basePackage = namespace + "." + category;
final String localPackage = className.contains(".") ? className.substring(0, className.lastIndexOf('.')) : "";
return Arrays.asList((localPackage.length() > 0
? basePackage + "." + localPackage
: basePackage).split("\\."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package io.vlingo.xoom.schemata.codegen.template.schematype.csharp;

import io.vlingo.xoom.codegen.template.TemplateData;
import io.vlingo.xoom.codegen.template.TemplateParameters;
import io.vlingo.xoom.schemata.codegen.ast.FieldDefinition;
import io.vlingo.xoom.schemata.codegen.ast.types.BasicType;
import io.vlingo.xoom.schemata.codegen.ast.types.ComputableType;
import io.vlingo.xoom.schemata.codegen.ast.types.Type;
import io.vlingo.xoom.schemata.codegen.ast.types.TypeDefinition;
import io.vlingo.xoom.schemata.codegen.ast.values.ListValue;
import io.vlingo.xoom.schemata.codegen.ast.values.NullValue;
import io.vlingo.xoom.schemata.codegen.ast.values.SingleValue;
import io.vlingo.xoom.schemata.codegen.ast.values.Value;
import io.vlingo.xoom.schemata.codegen.template.schematype.SchemaTypeTemplateData;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;

public class CSharpSchemaTypeTemplateData extends SchemaTypeTemplateData {

private final TypeDefinition type;
private final String version;

public static TemplateData from(final TypeDefinition type, final String version) {
return new CSharpSchemaTypeTemplateData(type, version);
}

private CSharpSchemaTypeTemplateData(final TypeDefinition type, final String version) {
this.type = type;
this.version = version;
}

@Override
public TemplateParameters parameters() {
List<Property> properties = properties();
return TemplateParameters
.with(CSharpSchemaTypeTemplateParameter.NAMESPACE, namespace())
.and(CSharpSchemaTypeTemplateParameter.IMPORTS, imports())
.and(CSharpSchemaTypeTemplateParameter.TYPE_NAME, typeName())
.and(CSharpSchemaTypeTemplateParameter.BASE_TYPE_NAME, baseTypeName())
.and(CSharpSchemaTypeTemplateParameter.PROPERTIES, properties)
.and(CSharpSchemaTypeTemplateParameter.NEEDS_CONSTRUCTOR, needsConstructor(properties))
.and(CSharpSchemaTypeTemplateParameter.NEEDS_DEFAULT_CONSTRUCTOR, needsDefaultConstructor(properties));
}

private String namespace() {
return packageSegments(type.fullyQualifiedTypeName, type.category.name()+"s")
.stream()
.map(p -> p.substring(0, 1).toUpperCase() + p.substring(1))
.collect(joining("."));
}

private List<String> imports() {
List<Property> properties = properties();
return Stream.of("System", "Vlingo.Lattice.Model", "Vlingo.Xoom.Common.Version")
.filter(i -> !i.equals("System") || properties.stream().anyMatch(p -> p.constructorInitializer.startsWith("DateTimeOffset.")))
.filter(i -> !i.equals("Vlingo.Xoom.Common.Version") || properties.stream().anyMatch(p -> p.constructorInitializer.startsWith("SemanticVersion.")))
.collect(Collectors.toList());
}

private String typeName() {
return type.typeName;
}

private String baseTypeName() {
return "DomainEvent";
}

private List<Property> properties() {
return type.children.stream()
.filter(c -> c instanceof FieldDefinition)
.map(c -> (FieldDefinition) c)
.map(this::toProperty)
.collect(Collectors.toList());
}

private boolean needsConstructor(final List<Property> properties) {
return properties.stream().anyMatch(f -> !f.isComputed);
}

private boolean needsDefaultConstructor(final List<Property> properties) {
return properties.size() != 0 && properties.stream().allMatch(f -> f.isComputed || f.defaultValue != null);
}

private String initializationOf(final FieldDefinition field, final TypeDefinition owner) {
Type type = field.type;
if (type instanceof ComputableType) {
switch (((ComputableType) type).typeName) {
case "type":
return String.format("\"%s\"", owner.typeName);
case "version":
return String.format("SemanticVersion.toValue(\"%s\")", this.version);
case "timestamp":
return "DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()";
}
}
return field.name;
}

private String type(final Type type) {
if (type instanceof BasicType) {
return primitive((BasicType) type);
} else if (type instanceof ComputableType) {
return computable((ComputableType) type);
}
return type.name();
}

private String primitive(final BasicType basicType) {
String result;
switch (basicType.typeName) {
case "boolean":
result = "bool";
break;
case "byte":
case "char":
case "short":
case "int":
case "long":
case "float":
case "double":
case "string":
result = basicType.typeName;
break;
default:
result = "object";
break;
}

return basicType.isArrayType() ? result + "[]" : result;
}

private String computable(final ComputableType computableType) {
switch (computableType.typeName) {
case "type":
return "string";
case "timestamp":
return "long";
case "version":
return "int";
default:
return "object";
}
}

private String cSharpLiteralOf(FieldDefinition field) {
Value<?> value = field.defaultValue.orElseGet(NullValue::new);

if(value instanceof NullValue) {
return null;
}

if(value instanceof SingleValue) {
return cSharpLiteralOf((SingleValue<?>) value);
}

if(value instanceof ListValue) {
return cSharpLiteralOf((ListValue<?>) value);
}

throw new IllegalStateException("Unsupported value type encountered");
}

private String cSharpLiteralOf(final SingleValue<?> value) {
return value.value().toString();
}

@SuppressWarnings("unchecked")
private String cSharpLiteralOf(final ListValue value) {
return value.value().stream()
.map(e -> ((SingleValue<?>) e).value())
.collect(joining(", ", "{ ", " }"))
.toString();
}

private Property toProperty(final FieldDefinition field) {
return new Property(
type(field.type),
field.name.substring(0, 1).toUpperCase() + field.name.substring(1),
field.name,
cSharpLiteralOf(field),
initializationOf(field, type),
field.type instanceof ComputableType
);
}

public static class Property {
public final String type;
public final String name;
public final String argumentName;
public final String defaultValue;
public final String constructorInitializer;
public final boolean isComputed;

public Property(final String type, final String name, final String argumentName, final String defaultValue, final String constructorInitializer, final boolean isComputed) {
this.type = type;
this.name = name;
this.argumentName = argumentName;
this.defaultValue = defaultValue;
this.constructorInitializer = constructorInitializer;
this.isComputed = isComputed;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.vlingo.xoom.schemata.codegen.template.schematype.csharp;

import io.vlingo.xoom.codegen.template.ParameterKey;

public enum CSharpSchemaTypeTemplateParameter implements ParameterKey {
NAMESPACE("namespace"),
TYPE_NAME("typeName"),
BASE_TYPE_NAME("baseTypeName"),
IMPORTS("imports"),
PROPERTIES("properties"),
NEEDS_CONSTRUCTOR("needsConstructor"),
NEEDS_DEFAULT_CONSTRUCTOR("needsDefaultConstructor");

public final String key;

private CSharpSchemaTypeTemplateParameter(final String key) {
this.key = key;
}

public String value() {
return this.key;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import io.vlingo.xoom.codegen.template.TemplateData;
import io.vlingo.xoom.codegen.template.TemplateParameters;
import io.vlingo.xoom.lattice.model.DomainEvent;
import io.vlingo.xoom.schemata.Schemata;
import io.vlingo.xoom.schemata.codegen.ast.FieldDefinition;
import io.vlingo.xoom.schemata.codegen.ast.types.BasicType;
import io.vlingo.xoom.schemata.codegen.ast.types.ComputableType;
Expand Down Expand Up @@ -63,20 +62,7 @@ private List<Field> computedFields(final List<Field> fields) {
}

private String packageName() {
final String category = type.category.name().toLowerCase();
final String reference = type.fullyQualifiedTypeName;
final String[] referenceParts = reference.split(Schemata.ReferenceSeparator);
if (referenceParts.length < Schemata.MinReferenceParts) {
throw new IllegalArgumentException("Invalid fully qualified type name. Valid type names look like this <organization>:<unit>:<context namespace>:<type name>[:<version>].");
}
final String namespace = referenceParts[2];
final String className = referenceParts[3];

final String basePackage = namespace + "." + category;
final String localPackage = className.contains(".") ? className.substring(0, className.lastIndexOf('.')) : "";
return localPackage.length() > 0
? basePackage + "." + localPackage
: basePackage;
return packageSegments(type.fullyQualifiedTypeName, type.category.name().toLowerCase()).stream().collect(joining("."));
}

private String typeName() {
Expand Down
31 changes: 31 additions & 0 deletions src/main/resources/codegen/c_sharp/SchemaType.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<#list imports as import>
using ${import};
</#list>

namespace ${namespace}
{
public sealed class ${typeName} : ${baseTypeName}
{
<#list properties as property>
public ${property.type} ${property.name} { get; }<#if property.defaultValue??> = ${property.defaultValue};</#if>

</#list>
<#if needsConstructor>
public ${typeName}(<#list properties?filter(f -> !f.isComputed) as property>${property.type} ${property.argumentName}<#sep>, </#list>) {
<#list properties as property>
${property.name} = ${property.constructorInitializer};
</#list>
}
</#if>
<#if needsDefaultConstructor>
<#if needsConstructor>

</#if>
public ${typeName}() {
<#list properties?filter(f -> f.isComputed) as property>
${property.name} = ${property.constructorInitializer};
</#list>
}
</#if>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@

import io.vlingo.xoom.codegen.TextExpectation;
import io.vlingo.xoom.schemata.codegen.TypeDefinitionCompilerActor;
import org.junit.Ignore;

@Ignore
public class CSharpCodeGenTests extends CodeGenSpecs {
@Override
protected TypeDefinitionCompilerActor compiler() {
Expand Down
Loading