Skip to content
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7077014
Test with
a-d Feb 25, 2025
5e50752
Revert changes
a-d Feb 25, 2025
592a389
Add fix
a-d Feb 25, 2025
0076de5
Add test
a-d Feb 25, 2025
ed8532a
Format
a-d Feb 26, 2025
5f8c291
Fix PMD
a-d Feb 26, 2025
947ee19
Fix merge
a-d Feb 26, 2025
54fb0b3
Delete datamodel/openapi/openapi-generator/src/test/resources/DataMod…
newtork Feb 26, 2025
9f8eb8a
Update datamodel/openapi/openapi-generator/src/test/java/com/sap/clou…
newtork Feb 26, 2025
b9c0813
Merge remote-tracking branch 'origin/main' into float-array-instead-o…
a-d Feb 27, 2025
a7dd05f
Merge
a-d Feb 27, 2025
4b51e58
Merge remote-tracking branch 'origin/float-array-instead-of-bigdecima…
a-d Feb 27, 2025
2b502a1
Adapting equals and hashcode generation for float[] (#737)
rpanackal Feb 28, 2025
d27fa30
Move out the custom gen feature login
rpanackal Feb 28, 2025
9966ed8
change access to package private
rpanackal Feb 28, 2025
fd64019
service loader pattern, visitor like pattern
a-d Feb 28, 2025
c6ea658
PoC
a-d Feb 28, 2025
693f5d0
Refine chained strategy pattern
a-d Mar 3, 2025
15919d9
Fix naming; add Javadoc
a-d Mar 3, 2025
83276ee
Fix log level
a-d Mar 4, 2025
9dcd3d4
Merge remote-tracking branch 'origin/main' into openapi/partial-gener…
a-d Mar 4, 2025
2b2ad23
Fix merge; Move files
a-d Mar 4, 2025
1fb0561
Fix PMD, re-order methods
a-d Mar 4, 2025
9118f7e
Minor format improvement
a-d Mar 4, 2025
a912116
Fix checkstyle
a-d Mar 4, 2025
231f62b
Fix PMD
a-d Mar 4, 2025
85bdc58
Merge remote-tracking branch 'origin/main' into openapi/partial-gener…
a-d Mar 10, 2025
2cb2e39
Revert refactoring
a-d Mar 10, 2025
4eed5eb
Revert "Revert refactoring"
a-d Mar 10, 2025
d5f637c
Merge remote-tracking branch 'origin/main' into openapi/generator-ext…
a-d Mar 12, 2025
d79d776
update class name
a-d Mar 12, 2025
2c0f7d8
Fix UseExcludeProperties
a-d Mar 12, 2025
70fc5ba
Fix FixRemoveUnusedComponents
a-d Mar 12, 2025
bed7181
Format
a-d Mar 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,76 +1,104 @@
package com.sap.cloud.sdk.datamodel.openapi.generator;

import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REDUNDANT_IS_BOOLEAN_PREFIX;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_FLOAT_ARRAYS;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_ONE_OF_CREATORS;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.languages.JavaClientCodegen;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationsMap;

import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import lombok.extern.slf4j.Slf4j;

@Slf4j
class CustomJavaClientCodegen extends JavaClientCodegen
{
private final List<GeneratorCustomization> customizations;
private final GenerationConfiguration config;
private static final Predicate<String> DOUBLE_IS_PATTERN = Pattern.compile("^isIs[A-Z]").asPredicate();
private static final Set<String> PRIMITIVES = Set.of("String", "Integer", "Long", "Double", "Float", "Byte");

public CustomJavaClientCodegen( @Nonnull final GenerationConfiguration config )
{
this.config = config;
this.customizations = GeneratorCustomization.getCustomizations(config);
}

private <HandlerT extends GeneratorCustomization.ChainableReturn<HandlerT>, ValueT> ValueT chainedContextReturn(
@Nonnull final Class<? extends HandlerT> handlerClass,
@Nonnull final HandlerT rootHandler,
@Nonnull final Function<GeneratorCustomization.ChainElementReturn<HandlerT, ValueT>, ValueT> initiator )
{
var chainedContext = rootHandler.<ValueT> chained(config, null);
for( final GeneratorCustomization customization : customizations ) {
if( handlerClass.isInstance(customization) ) {
chainedContext = handlerClass.cast(customization).chained(config, chainedContext);
}
}
return initiator.apply(chainedContext);
}

private <HandlerT extends GeneratorCustomization.ChainableVoid<HandlerT>> void chainedContextVoid(
@Nonnull final Class<? extends HandlerT> handlerClass,
@Nonnull final HandlerT rootHandler,
@Nonnull final Consumer<GeneratorCustomization.ChainElementVoid<HandlerT>> initiator )
{
var chainedContext = rootHandler.chained(config, null);
for( final GeneratorCustomization customization : customizations ) {
if( handlerClass.isInstance(customization) ) {
chainedContext = handlerClass.cast(customization).chained(config, chainedContext);
}
}
initiator.accept(chainedContext);
}

@Override
public void preprocessOpenAPI( @Nonnull final OpenAPI openAPI )
{
chainedContextVoid(
GeneratorCustomization.PreProcessOpenAPI.class,
( context, openAPI1 ) -> super.preprocessOpenAPI(openAPI1),
context -> context.get().preprocessOpenAPI(context, openAPI));
}

@Override
protected
void
updatePropertyForArray( @Nonnull final CodegenProperty property, @Nonnull final CodegenProperty innerProperty )
{
super.updatePropertyForArray(property, innerProperty);

if( USE_FLOAT_ARRAYS.isEnabled(config) && innerProperty.isNumber && property.isArray ) {
property.datatypeWithEnum = "float[]";
property.vendorExtensions.put("isPrimitiveArray", true);
}
chainedContextVoid(
GeneratorCustomization.UpdatePropertyForArray.class,
( context, property1, innerProperty1 ) -> super.updatePropertyForArray(property1, innerProperty1),
context -> context.get().updatePropertyForArray(context, property, innerProperty));
}

@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
@Override
@Nullable
public String toDefaultValue( @Nonnull final CodegenProperty cp, @Nonnull final Schema schema )
{
if( USE_FLOAT_ARRAYS.isEnabled(config) && "float[]".equals(cp.datatypeWithEnum) ) {
return null;
}
return super.toDefaultValue(cp, schema);
return chainedContextReturn(
GeneratorCustomization.ToDefaultValue.class,
( context, cp1, schema1 ) -> super.toDefaultValue(cp1, schema1),
context -> context.get().toDefaultValue(context, cp, schema));
}

@Override
@Nullable
public String toBooleanGetter( @Nullable final String name )
{
final String result = super.toBooleanGetter(name);
if( FIX_REDUNDANT_IS_BOOLEAN_PREFIX.isEnabled(config) && result != null && DOUBLE_IS_PATTERN.test(result) ) {
return "is" + result.substring(4);
}
return result;
return chainedContextReturn(
GeneratorCustomization.ToBooleanGetter.class,
( context, name1 ) -> super.toBooleanGetter(name1),
context -> context.get().toBooleanGetter(context, name));
}

// Custom processor to inject "x-return-nullable" extension
Expand All @@ -80,14 +108,10 @@ public String toBooleanGetter( @Nullable final String name )
OperationsMap
postProcessOperationsWithModels( @Nonnull final OperationsMap ops, @Nonnull final List<ModelMap> allModels )
{
for( final CodegenOperation op : ops.getOperations().getOperation() ) {
final var noContent =
op.isResponseOptional
|| op.responses == null
|| op.responses.stream().anyMatch(r -> "204".equals(r.code));
op.vendorExtensions.put("x-return-nullable", op.returnType != null && noContent);
}
return super.postProcessOperationsWithModels(ops, allModels);
return chainedContextReturn(
GeneratorCustomization.PostProcessOperationsWithModels.class,
( context, ops1, allModels1 ) -> super.postProcessOperationsWithModels(ops1, allModels1),
context -> context.get().postProcessOperationsWithModels(context, ops, allModels));
}

@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
Expand All @@ -97,64 +121,19 @@ protected void updateModelForComposedSchema(
@Nonnull final Schema schema,
@Nonnull final Map<String, Schema> allDefinitions )
{
super.updateModelForComposedSchema(m, schema, allDefinitions);

if( USE_ONE_OF_CREATORS.isEnabled(config) ) {
useCreatorsForInterfaceSubtypes(m);
}
}

/**
* Use JsonCreator for interface sub-types in case there are any primitives.
*
* @param m
* The model to update.
*/
private void useCreatorsForInterfaceSubtypes( @Nonnull final CodegenModel m )
{
if( m.discriminator != null ) {
return;
}
boolean useCreators = false;
for( final Set<String> candidates : List.of(m.anyOf, m.oneOf) ) {
int nonPrimitives = 0;
final var candidatesSingle = new HashSet<String>();
final var candidatesMultiple = new HashSet<String>();

for( final String candidate : candidates ) {
if( candidate.startsWith("List<") ) {
final var c1 = candidate.substring(5, candidate.length() - 1);
candidatesMultiple.add(c1);
useCreators = true;
} else {
candidatesSingle.add(candidate);
useCreators |= PRIMITIVES.contains(candidate);
if( !PRIMITIVES.contains(candidate) ) {
nonPrimitives++;
}
}
}
if( useCreators ) {
if( nonPrimitives > 1 ) {
final var msg =
"Generating interface with mixed multiple non-primitive and primitive sub-types: {}. Deserialization may not work.";
log.warn(msg, m.name);
}
candidates.clear();
final var monads = Map.of("single", candidatesSingle, "multiple", candidatesMultiple);
m.vendorExtensions.put("x-monads", monads);
m.vendorExtensions.put("x-is-one-of-interface", true); // enforce template usage
}
}
chainedContextVoid(
GeneratorCustomization.UpdateModelForComposedSchema.class,
( context, m1, schema1, allDef1 ) -> super.updateModelForComposedSchema(m1, schema1, allDef1),
context -> context.get().updateModelForComposedSchema(context, m, schema, allDefinitions));
}

@SuppressWarnings( { "rawtypes", "RedundantSuppression" } )
@Override
protected void updateModelForObject( @Nonnull final CodegenModel m, @Nonnull final Schema schema )
{
// Disable additional attributes to prevent model classes from extending "HashMap"
// SAP Cloud SDK offers custom field APIs to handle additional attributes already
schema.setAdditionalProperties(Boolean.FALSE);
super.updateModelForObject(m, schema);
chainedContextVoid(
GeneratorCustomization.UpdateModelForObject.class,
( context, m1, schema1 ) -> super.updateModelForObject(m1, schema1),
context -> context.get().updateModelForObject(context, m, schema));
}
}

This file was deleted.

Loading