diff --git a/changelog/@unreleased/pr-2088.v2.yml b/changelog/@unreleased/pr-2088.v2.yml new file mode 100644 index 000000000..893e24f75 --- /dev/null +++ b/changelog/@unreleased/pr-2088.v2.yml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: Refactor BeanGenerator#generateBeanType to move logic for creating + empty beans to separate method + links: + - https://github.com/palantir/conjure-java/pull/2088 diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java index 2b64a351b..8a4acf359 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java @@ -102,12 +102,16 @@ public static JavaFile generateBeanType( TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(prefixedName.getName()) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addAnnotations(safety) - .addFields(poetFields) - .addMethod(createConstructor(fields, poetFields)) - .addMethods(createGetters(fields, typesMap, options, safetyEvaluator)); + .addAnnotations(safety); + + if (poetFields.isEmpty()) { + addEmptyBean(typeBuilder, prefixedName, safety, objectClass, options); + } else { + typeBuilder + .addFields(poetFields) + .addMethod(createConstructor(fields, poetFields)) + .addMethods(createGetters(fields, typesMap, options, safetyEvaluator)); - if (!poetFields.isEmpty()) { boolean useCachedHashCode = useCachedHashCode(fields); typeBuilder .addMethod(MethodSpecs.createEquals(objectClass)) @@ -117,48 +121,59 @@ public static JavaFile generateBeanType( } else { typeBuilder.addMethod(MethodSpecs.createHashCode(poetFields)); } - } - typeBuilder.addMethod(MethodSpecs.createToString( - prefixedName.getName(), - fields.stream().map(EnrichedField::fieldName).collect(Collectors.toList())) - .toBuilder() - .addAnnotations(safety) - .build()); - - if (poetFields.size() <= MAX_NUM_PARAMS_FOR_FACTORY) { - typeBuilder.addMethod(createStaticFactoryMethod( - fields, - objectClass, - safetyEvaluator, - options.useStagedBuilders() && !options.useStrictStagedBuilders())); - } + typeBuilder.addMethod(MethodSpecs.createToString( + prefixedName.getName(), + fields.stream().map(EnrichedField::fieldName).collect(Collectors.toList())) + .toBuilder() + .addAnnotations(safety) + .build()); + + if (poetFields.size() <= MAX_NUM_PARAMS_FOR_FACTORY) { + typeBuilder.addMethod(createStaticFactoryMethod( + fields, + objectClass, + safetyEvaluator, + options.useStagedBuilders() && !options.useStrictStagedBuilders())); + } - if (!nonPrimitiveEnrichedFields.isEmpty()) { - typeBuilder - .addMethod(createValidateFields(nonPrimitiveEnrichedFields)) - .addMethod(createAddFieldIfMissing(nonPrimitiveEnrichedFields.size())); - } + if (!nonPrimitiveEnrichedFields.isEmpty()) { + typeBuilder + .addMethod(createValidateFields(nonPrimitiveEnrichedFields)) + .addMethod(createAddFieldIfMissing(nonPrimitiveEnrichedFields.size())); + } - if (poetFields.isEmpty()) { - // Need to add JsonSerialize annotation which indicates that the empty bean serializer should be used to - // serialize this class. Without this annotation no serializer will be set for this class, thus preventing - // serialization. - typeBuilder.addAnnotation(JsonSerialize.class).addField(createSingletonField(objectClass)); - if (!options.strictObjects()) { - typeBuilder.addAnnotation(AnnotationSpec.builder(JsonIgnoreProperties.class) - .addMember("ignoreUnknown", "$L", true) - .build()); + if (options.useStrictStagedBuilders()) { + BeanBuilderGenerator.addStrictStagedBuilder( + typeBuilder, + typeMapper, + safetyEvaluator, + objectClass, + builderClass, + typeDef, + typesMap, + options); + } else if (options.useStagedBuilders()) { + BeanBuilderGenerator.addStagedBuilder( + typeBuilder, + typeMapper, + safetyEvaluator, + objectClass, + builderClass, + typeDef, + typesMap, + options); + } else { + BeanBuilderGenerator.addBuilder( + typeBuilder, + typeMapper, + safetyEvaluator, + objectClass, + builderClass, + typeDef, + typesMap, + options); } - } else if (options.useStrictStagedBuilders()) { - BeanBuilderGenerator.addStrictStagedBuilder( - typeBuilder, typeMapper, safetyEvaluator, objectClass, builderClass, typeDef, typesMap, options); - } else if (options.useStagedBuilders()) { - BeanBuilderGenerator.addStagedBuilder( - typeBuilder, typeMapper, safetyEvaluator, objectClass, builderClass, typeDef, typesMap, options); - } else { - BeanBuilderGenerator.addBuilder( - typeBuilder, typeMapper, safetyEvaluator, objectClass, builderClass, typeDef, typesMap, options); } typeBuilder.addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(BeanGenerator.class)); @@ -170,6 +185,31 @@ public static JavaFile generateBeanType( .build(); } + private static void addEmptyBean( + TypeSpec.Builder typeBuilder, + com.palantir.conjure.spec.TypeName prefixedName, + ImmutableList safety, + ClassName objectClass, + Options options) { + typeBuilder.addMethod(createConstructor(ImmutableList.of(), ImmutableList.of())); + + typeBuilder.addMethod(MethodSpecs.createToString(prefixedName.getName(), Collections.emptyList()).toBuilder() + .addAnnotations(safety) + .build()); + + typeBuilder.addMethod(createStaticFactoryMethodForEmptyBean(objectClass)); + + // Need to add JsonSerialize annotation which indicates that the empty bean serializer should be used to + // serialize this class. Without this annotation no serializer will be set for this class, thus preventing + // serialization. + typeBuilder.addAnnotation(JsonSerialize.class).addField(createSingletonField(objectClass)); + if (!options.strictObjects()) { + typeBuilder.addAnnotation(AnnotationSpec.builder(JsonIgnoreProperties.class) + .addMember("ignoreUnknown", "$L", true) + .build()); + } + } + private static boolean useCachedHashCode(Collection fields) { if (fields.size() == 1) { EnrichedField field = Iterables.getOnlyElement(fields); @@ -331,37 +371,45 @@ private static MethodSpec createStaticFactoryMethod( ClassName objectClass, SafetyEvaluator safetyEvaluator, boolean useNonStrictStagedBuilders) { + if (fields.isEmpty()) { + createStaticFactoryMethodForEmptyBean(objectClass); + } + MethodSpec.Builder builder = MethodSpec.methodBuilder("of") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(objectClass); - if (fields.isEmpty()) { - builder.addAnnotation(ConjureAnnotations.delegatingJsonCreator()) - .addCode("return $L;", SINGLETON_INSTANCE_NAME); - } else { - builder.addCode("return builder()"); - fields.forEach(field -> builder.addParameter(ParameterSpec.builder( - getTypeNameWithoutOptional(field.poetSpec()), field.poetSpec().name) - .addAnnotations(ConjureAnnotations.safety(safetyEvaluator.getUsageTimeSafety(field.conjureDef()))) - .build())); - - Stream methodArgs = useNonStrictStagedBuilders - ? fields.stream() - .sorted(Comparator.comparing(BeanBuilderGenerator::stagedBuilderFieldShouldBeInFinalStage)) - : fields.stream(); - methodArgs.map(EnrichedField::poetSpec).forEach(spec -> { - if (isOptional(spec)) { - builder.addCode("\n .$L(Optional.of($L))", spec.name, spec.name); - } else { - builder.addCode("\n .$L($L)", spec.name, spec.name); - } - }); - builder.addCode("\n .build();\n"); - } + builder.addCode("return builder()"); + fields.forEach(field -> builder.addParameter(ParameterSpec.builder( + getTypeNameWithoutOptional(field.poetSpec()), field.poetSpec().name) + .addAnnotations(ConjureAnnotations.safety(safetyEvaluator.getUsageTimeSafety(field.conjureDef()))) + .build())); + + Stream methodArgs = useNonStrictStagedBuilders + ? fields.stream() + .sorted(Comparator.comparing(BeanBuilderGenerator::stagedBuilderFieldShouldBeInFinalStage)) + : fields.stream(); + methodArgs.map(EnrichedField::poetSpec).forEach(spec -> { + if (isOptional(spec)) { + builder.addCode("\n .$L(Optional.of($L))", spec.name, spec.name); + } else { + builder.addCode("\n .$L($L)", spec.name, spec.name); + } + }); + builder.addCode("\n .build();\n"); return builder.build(); } + private static MethodSpec createStaticFactoryMethodForEmptyBean(ClassName objectClass) { + return MethodSpec.methodBuilder("of") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(objectClass) + .addAnnotation(ConjureAnnotations.delegatingJsonCreator()) + .addCode("return $L;", SINGLETON_INSTANCE_NAME) + .build(); + } + private static MethodSpec createAddFieldIfMissing(int fieldCount) { ParameterizedTypeName listOfStringType = ParameterizedTypeName.get(List.class, String.class); ParameterSpec listParam =