diff --git a/.gitignore b/.gitignore index 4e46271843..fd7d72201b 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ csharp/sbe-generated/since-deprecated csharp/sbe-generated/order_check csharp/sbe-generated/mktdata/*.cs csharp/sbe-generated/uk_co_real_logic_sbe_benchmarks_fix +csharp/sbe-generated/test_message_schema csharp/sbe-tests/*.sbe csharp/nuget/ csharp/csharp.sln.DotSettings.user diff --git a/build.gradle b/build.gradle index 40bbbbef9c..9641c9972f 100644 --- a/build.gradle +++ b/build.gradle @@ -894,7 +894,8 @@ tasks.register('generateCSharpTestCodecs', JavaExec) { 'sbe.target.language': 'uk.co.real_logic.sbe.generation.csharp.CSharp', 'sbe.xinclude.aware': 'true', 'sbe.validation.xsd': validationXsdPath, - 'sbe.generate.precedence.checks': 'true') + 'sbe.generate.precedence.checks': 'true', + 'sbe.types.package.override': 'true') args = ['sbe-tool/src/test/resources/FixBinary.xml', 'sbe-tool/src/test/resources/issue435.xml', 'sbe-tool/src/test/resources/issue483.xml', @@ -912,15 +913,41 @@ tasks.register('generateCSharpTestDtos', JavaExec) { 'sbe.output.dir': 'csharp/sbe-generated', 'sbe.target.language': 'uk.co.real_logic.sbe.generation.csharp.CSharpDtos', 'sbe.xinclude.aware': 'true', - 'sbe.validation.xsd': validationXsdPath) + 'sbe.validation.xsd': validationXsdPath, + 'sbe.types.package.override': 'true') args = ['sbe-samples/src/main/resources/example-extension-schema.xml'] } +tasks.register('generateCSharpExplicitPackageOverrideCodecs', JavaExec) { + mainClass.set('uk.co.real_logic.sbe.SbeTool') + classpath = project(':sbe-tool').sourceSets.main.runtimeClasspath + systemProperties( + 'sbe.output.dir': 'csharp/sbe-generated', + 'sbe.target.language': 'uk.co.real_logic.sbe.generation.csharp.CSharp', + 'sbe.xinclude.aware': 'true', + 'sbe.generate.precedence.checks': 'true', + 'sbe.types.package.override': 'true') + args = ['sbe-tool/src/test/resources/explicit-package-test-schema.xml'] +} + +tasks.register('generateCSharpExplicitPackageOverrideDtos', JavaExec) { + mainClass.set('uk.co.real_logic.sbe.SbeTool') + classpath = project(':sbe-tool').sourceSets.main.runtimeClasspath + systemProperties( + 'sbe.output.dir': 'csharp/sbe-generated', + 'sbe.target.language': 'uk.co.real_logic.sbe.generation.csharp.CSharpDtos', + 'sbe.xinclude.aware': 'true', + 'sbe.types.package.override': 'true') + args = ['sbe-tool/src/test/resources/explicit-package-test-schema.xml'] +} + tasks.register('generateCSharpCodecs') { description = 'Generate csharp codecs' dependsOn 'generateCSharpTestCodecs', 'generateCSharpTestDtos', - 'generateCSharpCodecsWithXIncludes' + 'generateCSharpCodecsWithXIncludes', + 'generateCSharpExplicitPackageOverrideCodecs', + 'generateCSharpExplicitPackageOverrideDtos' } tasks.register('generateJavaIrCodecs', JavaExec) { diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java index c09152fabf..c979bede45 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/TargetCodeGeneratorLoader.java @@ -107,13 +107,21 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) final NamespaceOutputManager outputManager = new NamespaceOutputManager( outputDir, ir.applicableNamespace()); final boolean decodeUnknownEnumValues = Boolean.getBoolean(DECODE_UNKNOWN_ENUM_VALUES); + final boolean shouldSupportTypesPackageNames = Boolean.getBoolean(TYPES_PACKAGE_OVERRIDE); - final CodeGenerator codecGenerator = new CppGenerator(ir, decodeUnknownEnumValues, precedenceChecks(), + final CodeGenerator codecGenerator = new CppGenerator( + ir, + decodeUnknownEnumValues, + precedenceChecks(), + shouldSupportTypesPackageNames, outputManager); if (Boolean.getBoolean(CPP_GENERATE_DTOS)) { - final CodeGenerator dtoGenerator = new CppDtoGenerator(ir, outputManager); + final CodeGenerator dtoGenerator = new CppDtoGenerator( + ir, + shouldSupportTypesPackageNames, + outputManager); return () -> { codecGenerator.generate(); diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtoGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtoGenerator.java index 9c4064dbc9..b6f337a837 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtoGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtoGenerator.java @@ -50,27 +50,65 @@ public class CppDtoGenerator implements CodeGenerator private final Ir ir; private final OutputManager outputManager; + private final boolean shouldSupportTypesPackageNames; + private final Map namespaceByType = new HashMap<>(); /** * Create a new C++ DTO {@link CodeGenerator}. * - * @param ir for the messages and types. - * @param outputManager for generating the DTOs to. + * @param ir for the messages and types. + * @param shouldSupportTypesPackageNames generator support for types in their own package. + * @param outputManager for generating the DTOs to. */ - public CppDtoGenerator(final Ir ir, final OutputManager outputManager) + public CppDtoGenerator( + final Ir ir, + final boolean shouldSupportTypesPackageNames, + final OutputManager outputManager) { Verify.notNull(ir, "ir"); Verify.notNull(outputManager, "outputManager"); this.ir = ir; + this.shouldSupportTypesPackageNames = shouldSupportTypesPackageNames; this.outputManager = outputManager; } + private String[] fetchTypesPackageName(final Token token, final Ir ir) + { + if (!shouldSupportTypesPackageNames) + { + return ir.namespaces(); + } + + if (token.packageName() != null) + { + return Ir.getNamespaces(token.packageName()); + } + + return ir.namespaces(); + } + /** * {@inheritDoc} */ public void generate() throws IOException { + namespaceByType.clear(); + + if (shouldSupportTypesPackageNames) + { + for (final List tokens : ir.types()) + { + final Token token = tokens.get(0); + final String packageName = token.packageName(); + + if (packageName != null) + { + namespaceByType.put(token.applicableTypeName(), packageName); + } + } + } + generateDtosForTypes(); for (final List tokens : ir.messages()) @@ -116,13 +154,14 @@ public void generate() throws IOException final Set referencedTypes = generateTypesToIncludes(beginTypeTokensInSchema); referencedTypes.add(codecClassName); + final String[] namespaces = fetchTypesPackageName(msgToken, ir); out.append(generateDtoFileHeader( - ir.namespaces(), + namespaces, className, referencedTypes)); out.append(generateDocumentation(BASE_INDENT, msgToken)); classBuilder.appendTo(out); - out.append(CppUtil.closingBraces(ir.namespaces().length)); + out.append(CppUtil.closingBraces(namespaces.length)); out.append("#endif\n"); } } @@ -1732,7 +1771,8 @@ private void generateDtosForTypes() throws IOException private void generateComposite(final List tokens) throws IOException { - final String name = tokens.get(0).applicableTypeName(); + final Token token = tokens.get(0); + final String name = token.applicableTypeName(); final String className = formatDtoClassName(name); final String codecClassName = formatClassName(name); @@ -1741,8 +1781,9 @@ private void generateComposite(final List tokens) throws IOException final List compositeTokens = tokens.subList(1, tokens.size() - 1); final Set referencedTypes = generateTypesToIncludes(compositeTokens); referencedTypes.add(codecClassName); - out.append(generateDtoFileHeader(ir.namespaces(), className, referencedTypes)); - out.append(generateDocumentation(BASE_INDENT, tokens.get(0))); + final String[] namespaces = fetchTypesPackageName(token, ir); + out.append(generateDtoFileHeader(namespaces, className, referencedTypes)); + out.append(generateDocumentation(BASE_INDENT, token)); final ClassBuilder classBuilder = new ClassBuilder(className, BASE_INDENT); @@ -1754,14 +1795,15 @@ private void generateComposite(final List tokens) throws IOException codecClassName + "::sbeSchemaVersion()", BASE_INDENT + INDENT); classBuilder.appendTo(out); - out.append(CppUtil.closingBraces(ir.namespaces().length)); + out.append(CppUtil.closingBraces(namespaces.length)); out.append("#endif\n"); } } private void generateChoiceSet(final List tokens) throws IOException { - final String name = tokens.get(0).applicableTypeName(); + final Token token = tokens.get(0); + final String name = token.applicableTypeName(); final String className = formatDtoClassName(name); final String codecClassName = formatClassName(name); @@ -1770,7 +1812,8 @@ private void generateChoiceSet(final List tokens) throws IOException final List setTokens = tokens.subList(1, tokens.size() - 1); final Set referencedTypes = generateTypesToIncludes(setTokens); referencedTypes.add(codecClassName); - out.append(generateDtoFileHeader(ir.namespaces(), className, referencedTypes)); + final String[] namespaces = fetchTypesPackageName(token, ir); + out.append(generateDtoFileHeader(namespaces, className, referencedTypes)); out.append(generateDocumentation(BASE_INDENT, tokens.get(0))); final ClassBuilder classBuilder = new ClassBuilder(className, BASE_INDENT); @@ -1780,7 +1823,7 @@ private void generateChoiceSet(final List tokens) throws IOException generateChoiceSetEncodeWith(classBuilder, className, codecClassName, setTokens, BASE_INDENT + INDENT); classBuilder.appendTo(out); - out.append(CppUtil.closingBraces(ir.namespaces().length)); + out.append(CppUtil.closingBraces(namespaces.length)); out.append("#endif\n"); } } @@ -1965,7 +2008,7 @@ private static CharSequence typeWithFieldOptionality( } } - private static CharSequence generateDtoFileHeader( + private CharSequence generateDtoFileHeader( final CharSequence[] namespaces, final String className, final Collection typesToInclude) @@ -2010,6 +2053,32 @@ private static CharSequence generateDtoFileHeader( sb.append(String.join(" {\nnamespace ", namespaces)); sb.append(" {\n\n"); + if (shouldSupportTypesPackageNames && typesToInclude != null && !typesToInclude.isEmpty()) + { + final Set namespacesToUse = namespaceByType + .entrySet() + .stream() + .filter(e -> typesToInclude.contains(e.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toSet()); + + // remove the current namespace + namespacesToUse.remove(String.join(".", namespaces)); + + for (final String namespace : namespacesToUse) + { + sb + .append("using namespace ") + .append(namespace.replaceAll("\\.", "::")) + .append(";\n"); + } + + if (!namespacesToUse.isEmpty()) + { + sb.append("\n"); + } + } + return sb; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtos.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtos.java index 09dbfb052f..6e59b6dd06 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtos.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtos.java @@ -31,6 +31,6 @@ public class CppDtos implements TargetCodeGenerator */ public CodeGenerator newInstance(final Ir ir, final String outputDir) { - return new CppDtoGenerator(ir, new NamespaceOutputManager(outputDir, ir.applicableNamespace())); + return new CppDtoGenerator(ir, false, new NamespaceOutputManager(outputDir, ir.applicableNamespace())); } } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java index cc6921a0bb..8533a4df84 100755 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java @@ -33,6 +33,7 @@ import java.io.Writer; import java.nio.ByteOrder; import java.util.*; +import java.util.stream.Collectors; import static uk.co.real_logic.sbe.generation.Generators.toLowerFirstChar; import static uk.co.real_logic.sbe.generation.Generators.toUpperFirstChar; @@ -58,6 +59,8 @@ public class CppGenerator implements CodeGenerator private final boolean shouldDecodeUnknownEnumValues; private final PrecedenceChecks precedenceChecks; private final String precedenceChecksFlagName; + private final boolean shouldSupportTypesPackageNames; + private final Map namespaceByType = new HashMap<>(); /** * Create a new Cpp language {@link CodeGenerator}. @@ -72,6 +75,7 @@ public CppGenerator(final Ir ir, final boolean shouldDecodeUnknownEnumValues, fi ir, shouldDecodeUnknownEnumValues, PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + false, outputManager ); } @@ -79,15 +83,17 @@ public CppGenerator(final Ir ir, final boolean shouldDecodeUnknownEnumValues, fi /** * Create a new Go language {@link CodeGenerator}. * - * @param ir for the messages and types. - * @param shouldDecodeUnknownEnumValues generate support for unknown enum values when decoding. - * @param precedenceChecks whether and how to generate field precedence checks. - * @param outputManager for generating the codecs to. + * @param ir for the messages and types. + * @param shouldDecodeUnknownEnumValues generate support for unknown enum values when decoding. + * @param precedenceChecks whether and how to generate field precedence checks. + * @param shouldSupportTypesPackageNames generator support for types in their own package. + * @param outputManager for generating the codecs to. */ public CppGenerator( final Ir ir, final boolean shouldDecodeUnknownEnumValues, final PrecedenceChecks precedenceChecks, + final boolean shouldSupportTypesPackageNames, final OutputManager outputManager) { Verify.notNull(ir, "ir"); @@ -97,6 +103,7 @@ public CppGenerator( this.shouldDecodeUnknownEnumValues = shouldDecodeUnknownEnumValues; this.precedenceChecks = precedenceChecks; this.precedenceChecksFlagName = precedenceChecks.context().precedenceChecksFlagName(); + this.shouldSupportTypesPackageNames = shouldSupportTypesPackageNames; this.outputManager = outputManager; } @@ -140,6 +147,21 @@ private List generateTypeStubs() throws IOException return typesToInclude; } + private String[] fetchTypesPackageName(final Token token, final Ir ir) + { + if (!shouldSupportTypesPackageNames) + { + return ir.namespaces(); + } + + if (token.packageName() != null) + { + return Ir.getNamespaces(token.packageName()); + } + + return ir.namespaces(); + } + private List generateTypesToIncludes(final List tokens) { final List typesToInclude = new ArrayList<>(); @@ -167,6 +189,22 @@ private List generateTypesToIncludes(final List tokens) */ public void generate() throws IOException { + namespaceByType.clear(); + + if (shouldSupportTypesPackageNames) + { + for (final List tokens : ir.types()) + { + final Token token = tokens.get(0); + final String packageName = token.packageName(); + + if (packageName != null) + { + namespaceByType.put(token.applicableTypeName(), packageName); + } + } + } + generateMessageHeaderStub(); final List typesToInclude = generateTypeStubs(); @@ -192,7 +230,8 @@ public void generate() throws IOException final List varData = new ArrayList<>(); collectVarData(messageBody, i, varData); - out.append(generateFileHeader(ir.namespaces(), className, typesToInclude)); + final String[] namespaces = fetchTypesPackageName(msgToken, ir); + out.append(generateFileHeader(namespaces, className, typesToInclude)); out.append(generateClassDeclaration(className)); out.append(generateMessageFlyweightCode(className, msgToken, fieldPrecedenceModel)); out.append(generateFullyEncodedCheck(fieldPrecedenceModel)); @@ -205,7 +244,7 @@ public void generate() throws IOException sb.append(generateMessageLength(groups, varData, BASE_INDENT)); sb.append("};\n"); generateLookupTableDefinitions(sb, className, fieldPrecedenceModel); - sb.append(CppUtil.closingBraces(ir.namespaces().length)).append("#endif\n"); + sb.append(CppUtil.closingBraces(namespaces.length)).append("#endif\n"); out.append(sb); } } @@ -1277,7 +1316,8 @@ private void generateChoiceSet(final List tokens) throws IOException try (Writer out = outputManager.createOutput(bitSetName)) { - out.append(generateFileHeader(ir.namespaces(), bitSetName, null)); + final String[] namespaces = fetchTypesPackageName(token, ir); + out.append(generateFileHeader(namespaces, bitSetName, null)); out.append(generateClassDeclaration(bitSetName)); out.append(generateFixedFlyweightCode(bitSetName, token.encodedLength())); @@ -1322,7 +1362,7 @@ private void generateChoiceSet(final List tokens) throws IOException out.append(generateChoices(bitSetName, tokens.subList(1, tokens.size() - 1))); out.append(generateChoicesDisplay(bitSetName, tokens.subList(1, tokens.size() - 1))); out.append("};\n"); - out.append(CppUtil.closingBraces(ir.namespaces().length)).append("#endif\n"); + out.append(CppUtil.closingBraces(namespaces.length)).append("#endif\n"); } } @@ -1333,7 +1373,8 @@ private void generateEnum(final List tokens) throws IOException try (Writer out = outputManager.createOutput(enumName)) { - out.append(generateEnumFileHeader(ir.namespaces(), enumName)); + final String[] namespaces = fetchTypesPackageName(enumToken, ir); + out.append(generateEnumFileHeader(namespaces, enumName)); out.append(generateEnumDeclaration(enumName)); out.append(generateEnumValues(tokens.subList(1, tokens.size() - 1), enumToken)); @@ -1343,29 +1384,31 @@ private void generateEnum(final List tokens) throws IOException out.append(generateEnumDisplay(tokens.subList(1, tokens.size() - 1), enumToken)); out.append("};\n\n"); - out.append(CppUtil.closingBraces(ir.namespaces().length)).append("\n#endif\n"); + out.append(CppUtil.closingBraces(namespaces.length)).append("\n#endif\n"); } } private void generateComposite(final List tokens) throws IOException { - final String compositeName = formatClassName(tokens.get(0).applicableTypeName()); + final Token token = tokens.get(0); + final String compositeName = formatClassName(token.applicableTypeName()); try (Writer out = outputManager.createOutput(compositeName)) { - out.append(generateFileHeader(ir.namespaces(), compositeName, + final String[] namespaces = fetchTypesPackageName(token, ir); + out.append(generateFileHeader(namespaces, compositeName, generateTypesToIncludes(tokens.subList(1, tokens.size() - 1)))); out.append(generateClassDeclaration(compositeName)); - out.append(generateFixedFlyweightCode(compositeName, tokens.get(0).encodedLength())); + out.append(generateFixedFlyweightCode(compositeName, token.encodedLength())); out.append(generateCompositePropertyElements( compositeName, tokens.subList(1, tokens.size() - 1), BASE_INDENT)); out.append(generateCompositeDisplay( - tokens.get(0).applicableTypeName(), tokens.subList(1, tokens.size() - 1))); + token.applicableTypeName(), tokens.subList(1, tokens.size() - 1))); out.append("};\n\n"); - out.append(CppUtil.closingBraces(ir.namespaces().length)).append("\n#endif\n"); + out.append(CppUtil.closingBraces(namespaces.length)).append("\n#endif\n"); } } @@ -1614,7 +1657,7 @@ private static CharSequence generateTypeFieldNotPresentCondition(final int since sinceVersion); } - private static CharSequence generateFileHeader( + private CharSequence generateFileHeader( final CharSequence[] namespaces, final String className, final List typesToInclude) @@ -1720,6 +1763,32 @@ private static CharSequence generateFileHeader( sb.append(String.join(" {\nnamespace ", namespaces)); sb.append(" {\n\n"); + if (shouldSupportTypesPackageNames && typesToInclude != null && !typesToInclude.isEmpty()) + { + final Set namespacesToUse = namespaceByType + .entrySet() + .stream() + .filter(e -> typesToInclude.contains(e.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toSet()); + + // remove the current namespace + namespacesToUse.remove(String.join(".", namespaces)); + + for (final String namespace : namespacesToUse) + { + sb + .append("using namespace ") + .append(namespace.replaceAll("\\.", "::")) + .append(";\n"); + } + + if (!namespacesToUse.isEmpty()) + { + sb.append("\n"); + } + } + return sb; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java index 8a88cbce56..f13b9cfab6 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharp.java @@ -21,6 +21,8 @@ import uk.co.real_logic.sbe.generation.TargetCodeGeneratorLoader; import uk.co.real_logic.sbe.ir.Ir; +import static uk.co.real_logic.sbe.SbeTool.TYPES_PACKAGE_OVERRIDE; + /** * {@link CodeGenerator} factory for the CSharp target programming language. */ @@ -33,15 +35,19 @@ public class CSharp implements TargetCodeGenerator */ public CodeGenerator newInstance(final Ir ir, final String outputDir) { + final boolean shouldSupportTypesPackageNames = Boolean.getBoolean(TYPES_PACKAGE_OVERRIDE); final CSharpGenerator flyweightGenerator = new CSharpGenerator( ir, TargetCodeGeneratorLoader.precedenceChecks(), + shouldSupportTypesPackageNames, new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace())); if (GENERATE_DTOS) { - final CSharpDtoGenerator dtoGenerator = - new CSharpDtoGenerator(ir, new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace())); + final CSharpDtoGenerator dtoGenerator = new CSharpDtoGenerator( + ir, + shouldSupportTypesPackageNames, + new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace())); return () -> { diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtoGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtoGenerator.java index ad2e3068c7..b5225caa67 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtoGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtoGenerator.java @@ -30,7 +30,9 @@ import java.io.IOException; import java.io.Writer; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import static uk.co.real_logic.sbe.generation.csharp.CSharpUtil.*; @@ -49,27 +51,65 @@ public class CSharpDtoGenerator implements CodeGenerator private final Ir ir; private final OutputManager outputManager; + private final boolean shouldSupportTypesPackageNames; + private final Set packageNameByTypes = new HashSet<>(); /** * Create a new C# DTO {@link CodeGenerator}. * * @param ir for the messages and types. + * @param shouldSupportTypesPackageNames generator support for types in their own package. * @param outputManager for generating the DTOs to. */ - public CSharpDtoGenerator(final Ir ir, final OutputManager outputManager) + public CSharpDtoGenerator( + final Ir ir, + final boolean shouldSupportTypesPackageNames, + final OutputManager outputManager) { Verify.notNull(ir, "ir"); Verify.notNull(outputManager, "outputManager"); this.ir = ir; + this.shouldSupportTypesPackageNames = shouldSupportTypesPackageNames; this.outputManager = outputManager; } + private String fetchTypesPackageName(final Token token, final Ir ir) + { + if (!shouldSupportTypesPackageNames) + { + return ir.applicableNamespace(); + } + + if (token.packageName() != null) + { + return token.packageName(); + } + + return ir.applicableNamespace(); + } + /** * {@inheritDoc} */ public void generate() throws IOException { + packageNameByTypes.clear(); + + if (shouldSupportTypesPackageNames) + { + for (final List tokens : ir.types()) + { + final Token token = tokens.get(0); + final String packageName = token.packageName(); + + if (packageName != null) + { + packageNameByTypes.add(packageName); + } + } + } + generateDtosForTypes(); for (final List tokens : ir.messages()) @@ -110,7 +150,8 @@ public void generate() throws IOException try (Writer out = outputManager.createOutput(dtoClassName)) { out.append(generateFileHeader( - ir.applicableNamespace(), + fetchTypesPackageName(msgToken, ir), + packageNameByTypes, "#nullable enable\n\n", "using System.Collections.Generic;\n", "using System.Linq;\n")); @@ -1360,13 +1401,16 @@ private void generateDtosForTypes() throws IOException private void generateComposite(final List tokens) throws IOException { - final String name = tokens.get(0).applicableTypeName(); + final Token token = tokens.get(0); + final String name = token.applicableTypeName(); final String className = formatDtoClassName(name); final String codecClassName = formatClassName(name); try (Writer out = outputManager.createOutput(className)) { - out.append(generateFileHeader(ir.applicableNamespace(), + out.append(generateFileHeader( + fetchTypesPackageName(token, ir), + packageNameByTypes, "#nullable enable\n", "using System.Collections.Generic;\n", "using System.Linq;\n")); diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtos.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtos.java index 4fc0cc513f..118b8e8d45 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtos.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpDtos.java @@ -20,6 +20,8 @@ import uk.co.real_logic.sbe.generation.TargetCodeGenerator; import uk.co.real_logic.sbe.ir.Ir; +import static uk.co.real_logic.sbe.SbeTool.TYPES_PACKAGE_OVERRIDE; + /** * {@link CodeGenerator} factory for CSharp DTOs. */ @@ -30,6 +32,11 @@ public class CSharpDtos implements TargetCodeGenerator */ public CodeGenerator newInstance(final Ir ir, final String outputDir) { - return new CSharpDtoGenerator(ir, new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace())); + final boolean shouldSupportTypesPackageNames = Boolean.getBoolean(TYPES_PACKAGE_OVERRIDE); + return new CSharpDtoGenerator( + ir, + shouldSupportTypesPackageNames, + new CSharpNamespaceOutputManager(outputDir, ir.applicableNamespace()) + ); } } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java index 8c06689da8..af9979879a 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpGenerator.java @@ -60,6 +60,8 @@ public class CSharpGenerator implements CodeGenerator private final OutputManager outputManager; private final PrecedenceChecks precedenceChecks; private final String precedenceChecksFlagName; + private final boolean shouldSupportTypesPackageNames; + private final Set packageNameByTypes = new HashSet<>(); /** * Create a new C# language {@link CodeGenerator}. @@ -72,6 +74,7 @@ public CSharpGenerator(final Ir ir, final OutputManager outputManager) this( ir, PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + false, outputManager ); } @@ -79,13 +82,15 @@ public CSharpGenerator(final Ir ir, final OutputManager outputManager) /** * Create a new C# language {@link CodeGenerator}. * - * @param ir for the messages and types. - * @param precedenceChecks whether and how to perform field precedence checks. - * @param outputManager for generating the codecs to. + * @param ir for the messages and types. + * @param precedenceChecks whether and how to perform field precedence checks. + * @param shouldSupportTypesPackageNames generator support for types in their own package. + * @param outputManager for generating the codecs to. */ public CSharpGenerator( final Ir ir, final PrecedenceChecks precedenceChecks, + final boolean shouldSupportTypesPackageNames, final OutputManager outputManager) { Verify.notNull(ir, "ir"); @@ -94,6 +99,7 @@ public CSharpGenerator( this.ir = ir; this.precedenceChecks = precedenceChecks; this.precedenceChecksFlagName = precedenceChecks.context().precedenceChecksFlagName(); + this.shouldSupportTypesPackageNames = shouldSupportTypesPackageNames; this.outputManager = outputManager; } @@ -138,11 +144,42 @@ public void generateTypeStubs() throws IOException } } + private String fetchTypesPackageName(final Token token, final Ir ir) + { + if (!shouldSupportTypesPackageNames) + { + return ir.applicableNamespace(); + } + + if (token.packageName() != null) + { + return token.packageName(); + } + + return ir.applicableNamespace(); + } + /** * {@inheritDoc} */ public void generate() throws IOException { + packageNameByTypes.clear(); + + if (shouldSupportTypesPackageNames) + { + for (final List tokens : ir.types()) + { + final Token token = tokens.get(0); + final String packageName = token.packageName(); + + if (packageName != null) + { + packageNameByTypes.add(packageName); + } + } + } + generateMessageHeaderStub(); generateTypeStubs(); @@ -164,7 +201,7 @@ public void generate() throws IOException final List varData = new ArrayList<>(); collectVarData(messageBody, offset, varData); - out.append(generateFileHeader(ir.applicableNamespace())); + out.append(generateFileHeader(fetchTypesPackageName(msgToken, ir), packageNameByTypes)); out.append(generateDocumentation(BASE_INDENT, msgToken)); out.append(generateClassDeclaration(className)); out.append(generateMessageFlyweightCode(className, msgToken, fieldPrecedenceModel, BASE_INDENT)); @@ -662,7 +699,7 @@ private void generateBitSet(final List tokens) throws IOException try (Writer out = outputManager.createOutput(enumName)) { - out.append(generateFileHeader(ir.applicableNamespace())); + out.append(generateFileHeader(fetchTypesPackageName(enumToken, ir), packageNameByTypes)); out.append(generateDocumentation(INDENT, enumToken)); final String enumPrimitiveType = cSharpTypeName(enumToken.encoding().primitiveType()); out.append(generateEnumDeclaration(enumName, enumPrimitiveType, true)); @@ -682,7 +719,7 @@ private void generateEnum(final List tokens) throws IOException try (Writer out = outputManager.createOutput(enumName)) { - out.append(generateFileHeader(ir.applicableNamespace())); + out.append(generateFileHeader(fetchTypesPackageName(enumToken, ir), packageNameByTypes)); out.append(generateDocumentation(INDENT, enumToken)); final String enumPrimitiveType = cSharpTypeName(enumToken.encoding().primitiveType()); out.append(generateEnumDeclaration(enumName, enumPrimitiveType, false)); @@ -696,14 +733,15 @@ private void generateEnum(final List tokens) throws IOException private void generateComposite(final List tokens) throws IOException { - final String compositeName = CSharpUtil.formatClassName(tokens.get(0).applicableTypeName()); + final Token token = tokens.get(0); + final String compositeName = CSharpUtil.formatClassName(token.applicableTypeName()); try (Writer out = outputManager.createOutput(compositeName)) { - out.append(generateFileHeader(ir.applicableNamespace())); - out.append(generateDocumentation(INDENT, tokens.get(0))); + out.append(generateFileHeader(fetchTypesPackageName(token, ir), packageNameByTypes)); + out.append(generateDocumentation(INDENT, token)); out.append(generateClassDeclaration(compositeName)); - out.append(generateFixedFlyweightCode(tokens.get(0).encodedLength())); + out.append(generateFixedFlyweightCode(token.encodedLength())); out.append(generateCompositePropertyElements(tokens.subList(1, tokens.size() - 1), BASE_INDENT)); out.append(generateCompositeDisplay(tokens)); @@ -798,7 +836,7 @@ private void generateMetaAttributeEnum() throws IOException { try (Writer out = outputManager.createOutput(META_ATTRIBUTE_ENUM)) { - out.append(generateFileHeader(ir.applicableNamespace())); + out.append(generateFileHeader(ir.applicableNamespace(), null)); out.append( INDENT + "public enum MetaAttribute\n" + diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java index 31c4444feb..a80ae7a295 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/csharp/CSharpUtil.java @@ -24,6 +24,7 @@ import java.util.EnumMap; import java.util.Map; +import java.util.Set; /** * Utilities for mapping between IR and the C# language. @@ -87,8 +88,26 @@ static String generateLiteral(final PrimitiveType type, final String value) return literal; } - static CharSequence generateFileHeader(final String packageName, final String... topLevelStatements) + static CharSequence generateFileHeader( + final String packageName, + final Set packageNamesByTypes, + final String... topLevelStatements) { + final StringBuilder additionalUsingBuilder = new StringBuilder(); + if (packageNamesByTypes != null && !packageNamesByTypes.isEmpty()) + { + packageNamesByTypes + .stream() + .filter(p -> !packageName.equals(p)) + .forEach(p -> + additionalUsingBuilder + .append("using ") + .append(formatNamespace(p)) + .append(";\n")); + + additionalUsingBuilder.append("\n"); + } + return String.format( "// \n" + "// Generated SBE (Simple Binary Encoding) message codec\n" + @@ -98,9 +117,11 @@ static CharSequence generateFileHeader(final String packageName, final String... "using System;\n" + "using System.Text;\n" + "using Org.SbeTool.Sbe.Dll;\n\n" + - "namespace %2$s\n" + + "%2$s" + + "namespace %3$s\n" + "{\n", String.join("", topLevelStatements), + additionalUsingBuilder, formatNamespace(packageName)); } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/Ir.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/Ir.java index 3587a8a05f..87eba20050 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/Ir.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/ir/Ir.java @@ -79,14 +79,7 @@ public Ir( captureTypes(headerTokens, 0, headerTokens.size() - 1); - if ("true".equals(System.getProperty(SbeTool.CPP_NAMESPACES_COLLAPSE))) - { - this.namespaces = new String[]{ (namespaceName == null ? packageName : namespaceName).replace(".", "_") }; - } - else - { - this.namespaces = Pattern.compile("\\.").split(namespaceName == null ? packageName : namespaceName); - } + this.namespaces = Ir.getNamespaces(namespaceName == null ? packageName : namespaceName); } /** @@ -280,6 +273,25 @@ else if (signal.name().startsWith("END_")) } } + /** + * Construct an array of namespace strings based on the supplied namespaceName, + * accounting for the CPP_NAMESPACES_COLLAPSE option. + * + * @param namespaceName a namespace name that may contain period-separated nested namespaces + * @return an array of nested namespace strings + */ + public static String[] getNamespaces(final String namespaceName) + { + if ("true".equals(System.getProperty(SbeTool.CPP_NAMESPACES_COLLAPSE))) + { + return new String[]{ namespaceName.replace(".", "_") }; + } + else + { + return Pattern.compile("\\.").split(namespaceName); + } + } + private void captureTypes(final List tokens, final int beginIndex, final int endIndex) { for (int i = beginIndex; i <= endIndex; i++) diff --git a/sbe-tool/src/propertyTest/java/uk/co/real_logic/sbe/properties/DtosPropertyTest.java b/sbe-tool/src/propertyTest/java/uk/co/real_logic/sbe/properties/DtosPropertyTest.java index 7cb1aa03a1..a8341e578c 100644 --- a/sbe-tool/src/propertyTest/java/uk/co/real_logic/sbe/properties/DtosPropertyTest.java +++ b/sbe-tool/src/propertyTest/java/uk/co/real_logic/sbe/properties/DtosPropertyTest.java @@ -152,7 +152,7 @@ void csharpDtoEncodeShouldBeTheInverseOfDtoDecode( { new CSharpGenerator(encodedMessage.ir(), outputManager) .generate(); - new CSharpDtoGenerator(encodedMessage.ir(), outputManager) + new CSharpDtoGenerator(encodedMessage.ir(), false, outputManager) .generate(); } catch (final Exception generationException) @@ -214,7 +214,7 @@ void cppDtoEncodeShouldBeTheInverseOfDtoDecode( { new CppGenerator(encodedMessage.ir(), true, outputManager) .generate(); - new CppDtoGenerator(encodedMessage.ir(), outputManager) + new CppDtoGenerator(encodedMessage.ir(), false, outputManager) .generate(); } catch (final Exception generationException) diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java index 8b3575e0ab..b8c969c8b8 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/cpp/CppGeneratorTest.java @@ -18,6 +18,7 @@ import org.agrona.generation.StringWriterOutputManager; import org.junit.jupiter.api.Test; import uk.co.real_logic.sbe.Tests; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; import uk.co.real_logic.sbe.ir.Ir; import uk.co.real_logic.sbe.xml.IrGenerator; import uk.co.real_logic.sbe.xml.MessageSchema; @@ -28,6 +29,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse; class CppGeneratorTest @@ -72,4 +74,44 @@ void shouldUseConstexprWhenInitializingSemanticVersion() throws Exception assertThat(source, containsString("static constexpr const char* SBE_SEMANTIC_VERSION = \"5.2\"")); } } + + @Test + void dtosShouldReferenceTypesInDifferentPackages() throws Exception + { + try (InputStream in = Tests.getLocalResource("explicit-package-test-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final IrGenerator irg = new IrGenerator(); + final Ir ir = irg.generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + final CppGenerator generator = new CppGenerator( + ir, + false, + PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + true, + outputManager); + generator.generate(); + + final CppDtoGenerator dtoGenerator = new CppDtoGenerator(ir, true, outputManager); + dtoGenerator.generate(); + + final java.util.Map sources = outputManager.getSources(); + assertNotNull(sources.get("test.message.schema.TestMessageDto")); + assertNotNull(sources.get("test.message.schema.MessageHeaderDto")); + assertNotNull(sources.get("test.message.schema.CarDto")); + assertNotNull(sources.get("test.message.schema.EngineDto")); + + String source; + + source = sources.get("test.message.schema.TestMessageDto").toString(); + assertThat(source, containsString("using namespace outside::schema;")); + + source = sources.get("test.message.schema.TestMessage").toString(); + assertThat(source, containsString("using namespace outside::schema;")); + assertThat(source, containsString("using namespace test::message::schema::common;")); + } + } } diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java new file mode 100644 index 0000000000..295ddd293b --- /dev/null +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/csharp/CSharpGeneratorTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2024 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.co.real_logic.sbe.generation.csharp; + +import org.agrona.generation.StringWriterOutputManager; +import org.junit.jupiter.api.Test; +import uk.co.real_logic.sbe.Tests; +import uk.co.real_logic.sbe.generation.common.PrecedenceChecks; +import uk.co.real_logic.sbe.ir.Ir; +import uk.co.real_logic.sbe.xml.IrGenerator; +import uk.co.real_logic.sbe.xml.MessageSchema; +import uk.co.real_logic.sbe.xml.ParserOptions; + +import java.io.InputStream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse; + +class CSharpGeneratorTest +{ + @Test + void dtosShouldReferenceTypesInDifferentPackages() throws Exception + { + try (InputStream in = Tests.getLocalResource("explicit-package-test-schema.xml")) + { + final ParserOptions options = ParserOptions.builder().stopOnError(true).build(); + final MessageSchema schema = parse(in, options); + final IrGenerator irg = new IrGenerator(); + final Ir ir = irg.generate(schema); + final StringWriterOutputManager outputManager = new StringWriterOutputManager(); + outputManager.setPackageName(ir.applicableNamespace()); + + final CSharpGenerator generator = new CSharpGenerator( + ir, + PrecedenceChecks.newInstance(new PrecedenceChecks.Context()), + true, + outputManager); + generator.generate(); + + final CSharpDtoGenerator dtoGenerator = new CSharpDtoGenerator(ir, true, outputManager); + dtoGenerator.generate(); + + final java.util.Map sources = outputManager.getSources(); + + assertNotNull(sources.get("test.message.schema.TestMessageDto")); + assertNotNull(sources.get("test.message.schema.MessageHeaderDto")); + assertNotNull(sources.get("test.message.schema.CarDto")); + assertNotNull(sources.get("test.message.schema.EngineDto")); + + String source; + + source = sources.get("test.message.schema.TestMessageDto").toString(); + assertThat(source, containsString("using Outside.Schema;")); + + source = sources.get("test.message.schema.TestMessage").toString(); + assertThat(source, containsString("using Outside.Schema;")); + assertThat(source, containsString("using Test.Message.Schema.Common;")); + } + } +}