From 4d37f331baaf9ee3f7211c6857af3d21e04964e4 Mon Sep 17 00:00:00 2001 From: Nate Bradac <38217740+nbradac@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:59:28 -0500 Subject: [PATCH] [Java] DTOs respect the optional package field in the schema definition (#1022) * [Java] DTOs respect the optional package field in the schema definition * [Java] checkstyle --- .../generation/TargetCodeGeneratorLoader.java | 8 +- .../sbe/generation/cpp/CppDtoGenerator.java | 4 +- .../sbe/generation/java/JavaDtoGenerator.java | 85 ++++++++++++++++--- .../sbe/generation/java/JavaDtos.java | 7 +- .../sbe/properties/DtosPropertyTest.java | 2 +- .../generation/java/JavaGeneratorTest.java | 36 ++++++++ 6 files changed, 126 insertions(+), 16 deletions(-) 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 4edea4f8b4..c09152fabf 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 @@ -52,6 +52,7 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) { final JavaOutputManager outputManager = new JavaOutputManager(outputDir, ir.applicableNamespace()); + final boolean shouldSupportTypesPackageNames = Boolean.getBoolean(TYPES_PACKAGE_OVERRIDE); final JavaGenerator codecGenerator = new JavaGenerator( ir, System.getProperty(JAVA_ENCODING_BUFFER_TYPE, JAVA_DEFAULT_ENCODING_BUFFER_TYPE), @@ -59,13 +60,16 @@ public CodeGenerator newInstance(final Ir ir, final String outputDir) Boolean.getBoolean(JAVA_GROUP_ORDER_ANNOTATION), Boolean.getBoolean(JAVA_GENERATE_INTERFACES), Boolean.getBoolean(DECODE_UNKNOWN_ENUM_VALUES), - Boolean.getBoolean(TYPES_PACKAGE_OVERRIDE), + shouldSupportTypesPackageNames, precedenceChecks(), outputManager); if (Boolean.getBoolean(JAVA_GENERATE_DTOS)) { - final JavaDtoGenerator dtoGenerator = new JavaDtoGenerator(ir, outputManager); + final JavaDtoGenerator dtoGenerator = new JavaDtoGenerator( + 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 39b59412e5..9c4064dbc9 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 @@ -41,7 +41,7 @@ import static uk.co.real_logic.sbe.ir.GenerationUtil.collectVarData; /** - * DTO generator for the CSharp programming language. + * DTO generator for the C++ programming language. */ public class CppDtoGenerator implements CodeGenerator { @@ -52,7 +52,7 @@ public class CppDtoGenerator implements CodeGenerator private final OutputManager outputManager; /** - * Create a new C# DTO {@link CodeGenerator}. + * Create a new C++ DTO {@link CodeGenerator}. * * @param ir for the messages and types. * @param outputManager for generating the DTOs to. diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtoGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtoGenerator.java index b922bd14ed..baf9f70342 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtoGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtoGenerator.java @@ -24,12 +24,14 @@ import uk.co.real_logic.sbe.ir.Token; import org.agrona.LangUtil; import org.agrona.Verify; -import org.agrona.generation.OutputManager; +import org.agrona.generation.DynamicPackageOutputManager; 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.Generators.toLowerFirstChar; @@ -49,16 +51,23 @@ public class JavaDtoGenerator implements CodeGenerator private static final String BASE_INDENT = ""; private final Ir ir; - private final OutputManager outputManager; + private final DynamicPackageOutputManager outputManager; + private final boolean shouldSupportTypesPackageNames; + private final Set packageNameByTypes = new HashSet<>(); /** - * Create a new C# DTO {@link CodeGenerator}. + * Create a new Java 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 JavaDtoGenerator(final Ir ir, final OutputManager outputManager) + public JavaDtoGenerator( + final Ir ir, + final boolean shouldSupportTypesPackageNames, + final DynamicPackageOutputManager outputManager) { + this.shouldSupportTypesPackageNames = shouldSupportTypesPackageNames; Verify.notNull(ir, "ir"); Verify.notNull(outputManager, "outputManager"); @@ -66,6 +75,24 @@ public JavaDtoGenerator(final Ir ir, final OutputManager outputManager) this.outputManager = outputManager; } + /** + * Fetch the type's explicit package - if set and should be supported. + * + * @param token the 0-th token of the type. + * @param ir the intermediate representation. + * @return the overridden package name of the type if set and supported, or {@link Ir#applicableNamespace()}. + */ + private String fetchTypesPackageName(final Token token, final Ir ir) + { + if (shouldSupportTypesPackageNames && token.packageName() != null) + { + outputManager.setPackageName(token.packageName()); + return token.packageName(); + } + + return ir.applicableNamespace(); + } + /** * {@inheritDoc} */ @@ -110,9 +137,10 @@ public void generate() throws IOException generateDisplay(classBuilder, encoderClassName, "computeEncodedLength()", BASE_INDENT + INDENT); + final String packageName = fetchTypesPackageName(msgToken, ir); try (Writer out = outputManager.createOutput(dtoClassName)) { - out.append(generateDtoFileHeader(ir.applicableNamespace())); + out.append(generateDtoFileHeader(packageName)); out.append("import org.agrona.DirectBuffer;\n"); out.append("import org.agrona.MutableDirectBuffer;\n"); out.append("import org.agrona.concurrent.UnsafeBuffer;\n\n"); @@ -1582,6 +1610,19 @@ private static String formatDtoClassName(final String name) private void generateDtosForTypes() throws IOException { + if (shouldSupportTypesPackageNames) + { + for (final List tokens : ir.types()) + { + final String packageName = tokens.get(0).packageName(); + + if (packageName != null) + { + packageNameByTypes.add(packageName); + } + } + } + for (final List tokens : ir.types()) { switch (tokens.get(0).signal()) @@ -1607,10 +1648,11 @@ private void generateComposite(final List tokens) throws IOException final String encoderClassName = encoderName(name); final String decoderClassName = decoderName(name); + final String packageName = fetchTypesPackageName(tokens.get(0), ir); try (Writer out = outputManager.createOutput(className)) { final List compositeTokens = tokens.subList(1, tokens.size() - 1); - out.append(generateDtoFileHeader(ir.applicableNamespace())); + out.append(generateDtoFileHeader(packageName)); out.append("import org.agrona.DirectBuffer;\n"); out.append("import org.agrona.MutableDirectBuffer;\n"); out.append("import org.agrona.concurrent.UnsafeBuffer;\n\n"); @@ -1638,10 +1680,11 @@ private void generateChoiceSet(final List tokens) throws IOException final String encoderClassName = encoderName(name); final String decoderClassName = decoderName(name); + final String packageName = fetchTypesPackageName(tokens.get(0), ir); try (Writer out = outputManager.createOutput(className)) { final List setTokens = tokens.subList(1, tokens.size() - 1); - out.append(generateDtoFileHeader(ir.applicableNamespace())); + out.append(generateDtoFileHeader(packageName)); out.append(generateDocumentation(BASE_INDENT, tokens.get(0))); final ClassBuilder classBuilder = new ClassBuilder(className, BASE_INDENT, "public final"); @@ -1795,13 +1838,35 @@ private void generateCompositePropertyElements( } } - private static CharSequence generateDtoFileHeader(final String packageName) + private static StringBuilder generateImportStatements(final Set packages, final String currentPackage) + { + final StringBuilder importStatements = new StringBuilder(); + + for (final String candidatePackage : packages) + { + if (!candidatePackage.equals(currentPackage)) + { + importStatements.append("import ").append(candidatePackage).append(".*;\n"); + } + } + + if (!importStatements.isEmpty()) + { + importStatements.append("\n\n"); + } + + return importStatements; + } + + private CharSequence generateDtoFileHeader(final String packageName) { final StringBuilder sb = new StringBuilder(); sb.append("/* Generated SBE (Simple Binary Encoding) message DTO */\n"); sb.append("package ").append(packageName).append(";\n\n"); + sb.append(generateImportStatements(packageNameByTypes, packageName)); + return sb; } diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtos.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtos.java index 45eeccff93..3d4dc61482 100644 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtos.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/java/JavaDtos.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 Java DTOs. */ @@ -30,6 +32,9 @@ public class JavaDtos implements TargetCodeGenerator */ public CodeGenerator newInstance(final Ir ir, final String outputDir) { - return new JavaDtoGenerator(ir, new JavaOutputManager(outputDir, ir.applicableNamespace())); + return new JavaDtoGenerator( + ir, + Boolean.getBoolean(TYPES_PACKAGE_OVERRIDE), + new JavaOutputManager(outputDir, ir.applicableNamespace())); } } 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 8df61fe2b3..7cb1aa03a1 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 @@ -87,7 +87,7 @@ void javaDtoEncodeShouldBeTheInverseOfDtoDecode( outputManager) .generate(); - new JavaDtoGenerator(encodedMessage.ir(), outputManager) + new JavaDtoGenerator(encodedMessage.ir(), false, outputManager) .generate(); } catch (final Exception generationException) diff --git a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java index e93a7c1eba..68679c15a1 100644 --- a/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java +++ b/sbe-tool/src/test/java/uk/co/real_logic/sbe/generation/java/JavaGeneratorTest.java @@ -601,6 +601,42 @@ void shouldCreateTypesInSamePackageIfSupportDisabled() throws Exception } } + @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(); + ir = irg.generate(schema); + + outputManager.clear(); + outputManager.setPackageName(ir.applicableNamespace()); + + final JavaGenerator generator = new JavaGenerator( + ir, BUFFER_NAME, READ_ONLY_BUFFER_NAME, false, false, false, true, outputManager + ); + generator.generate(); + + final JavaDtoGenerator javaDtoGenerator = new JavaDtoGenerator(ir, true, outputManager); + javaDtoGenerator.generate(); + + final Map sources = outputManager.getSources(); + assertNotNull(sources.get("test.message.schema.TestMessageDto")); + assertNotNull(sources.get("test.message.schema.MessageHeaderDto")); + assertNotNull(sources.get("test.message.schema.common.CarDto")); + assertNotNull(sources.get("test.message.schema.common.EngineDto")); + assertNotNull(sources.get("outside.schema.FuelSpecDto")); + assertNotNull(sources.get("outside.schema.DaysDto")); + assertNotNull(sources.get("outside.schema.FuelTypeDto")); + + assertNotNull(compile("test.message.schema.TestMessageDto")); + assertNotNull(compile("test.message.schema.common.CarDto")); + assertNotNull(compile("outside.schema.FuelSpecDto")); + } + } + @ParameterizedTest @CsvSource( {