Repository: microsoft/kiota (csharp)

Issue:
[Java] Composed type wrapper model classes contain underscores
Haven't reduced it yet, but you can reproduce the issue with:

```bash
kiota generate --openapi https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml --output tmp --language java --clean-output true --clear-cache true
```

You get a successful output and that's great, unfortunately, there are static inner classes generated with names like: `CreateImageRequest_model` which should be `CreateImageRequestModel` instead.

Pull request:
I finally found the time to get to this 😅 
Fixes #3824

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6107044483..a688cef2df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Fixed a bug where the discriminator validation rule would report false positives on nullable union types.
 - Fixed a bug where the order of enum declaration might results in a missing enum type. [#3935](https://github.com/microsoft/kiota/issues/3935)
+- Fixed java generating compound types with underscore in the name. [#3824](https://github.com/microsoft/kiota/issues/3824)
 
 ## [1.9.1] - 2023-12-13
 
@@ -1202,5 +1203,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Initial GitHub release
-
-
diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs
index f483b7bb06..7dc512de54 100644
--- a/src/Kiota.Builder/Refiners/JavaRefiner.cs
+++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs
@@ -38,6 +38,13 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
                         IsExternal = true
                     }
                 });
+            ConvertUnionTypesToWrapper(generatedCode,
+                _configuration.UsesBackingStore,
+                s => s.ToFirstCharacterLowerCase(),
+                true,
+                SerializationNamespaceName,
+                "ComposedTypeWrapper"
+            );
             var reservedNamesProvider = new JavaReservedNamesProvider();
             CorrectNames(generatedCode, s =>
             {
@@ -58,13 +65,6 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
                 GenerationLanguage.Java);
             cancellationToken.ThrowIfCancellationRequested();
             RemoveCancellationParameter(generatedCode);
-            ConvertUnionTypesToWrapper(generatedCode,
-                _configuration.UsesBackingStore,
-                s => s.ToFirstCharacterLowerCase(),
-                true,
-                SerializationNamespaceName,
-                "ComposedTypeWrapper"
-            );
             CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
             cancellationToken.ThrowIfCancellationRequested();
             ReplaceBinaryByNativeType(generatedCode, "InputStream", "java.io", true, true);
diff --git a/src/Kiota.Builder/Refiners/RubyRefiner.cs b/src/Kiota.Builder/Refiners/RubyRefiner.cs
index 2af49e6cb2..90e5864dd2 100644
--- a/src/Kiota.Builder/Refiners/RubyRefiner.cs
+++ b/src/Kiota.Builder/Refiners/RubyRefiner.cs
@@ -39,6 +39,10 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
             var suffix = "Model";
             DisambiguateClassesWithNamespaceNames(generatedCode, classesToDisambiguate, suffix);
             UpdateReferencesToDisambiguatedClasses(generatedCode, classesToDisambiguate, suffix);
+            ConvertUnionTypesToWrapper(generatedCode,
+                _configuration.UsesBackingStore,
+                static s => s
+            );
             var reservedNamesProvider = new RubyReservedNamesProvider();
             CorrectNames(generatedCode, s =>
             {
@@ -55,10 +59,6 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
                 FlattenModelsNamespaces(modelsNS, modelsNS);
             AddPropertiesAndMethodTypesImports(generatedCode, false, false, true);
             RemoveCancellationParameter(generatedCode);
-            ConvertUnionTypesToWrapper(generatedCode,
-                _configuration.UsesBackingStore,
-                static s => s
-            );
             cancellationToken.ThrowIfCancellationRequested();
             AddParsableImplementsForModelClasses(generatedCode, "MicrosoftKiotaAbstractions::Parsable");
             AddDefaultImports(generatedCode, defaultUsingEvaluators);


Test patch:
diff --git a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
index 386338160f..33aecaaccd 100644
--- a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
+++ b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
@@ -134,7 +134,14 @@ public async Task GeneratesIdiomaticChildrenNames(GenerationLanguage language)
         };
         await new KiotaBuilder(logger, configuration, _httpClient).GenerateClientAsync(new());
 
+        var fullText = "";
+        foreach (var file in Directory.GetFiles(OutputPath, "*.*", SearchOption.AllDirectories))
+        {
+            fullText += File.ReadAllText(file);
+        }
+
         Assert.Empty(Directory.GetFiles(OutputPath, "*_*", SearchOption.AllDirectories));
+        Assert.DoesNotContain("_", fullText);
     }
     [InlineData(GenerationLanguage.CSharp)]
     [InlineData(GenerationLanguage.Go)]
diff --git a/tests/Kiota.Builder.IntegrationTests/NoUnderscoresInModel.yaml b/tests/Kiota.Builder.IntegrationTests/NoUnderscoresInModel.yaml
index 9e025211d5..2dfe692f35 100644
--- a/tests/Kiota.Builder.IntegrationTests/NoUnderscoresInModel.yaml
+++ b/tests/Kiota.Builder.IntegrationTests/NoUnderscoresInModel.yaml
@@ -1,11 +1,25 @@
 openapi: 3.0.0
+servers:
+  - url: https://localhost
 info:
   title: Test
   version: 1.0.0
   description: something
+  contact:
+    name: Microsoft
+tags:
+  - name: metrics
+    description: "The metrics API"
+  - name: permissions
+    description: "The permissions API"
+  - name: image
+    description: "The image API"
 paths:
   /api/metrics/v1:
     get:
+      operationId: getMetrics
+      tags:
+        - "metrics"
       responses:
         "200":
           content:
@@ -16,6 +30,9 @@ paths:
       description: Returns a test list
   /api/permissions/v1:
     get:
+      operationId: getPermissions
+      tags:
+        - "permissions"
       responses:
         "200":
           content:
@@ -24,6 +41,25 @@ paths:
                 $ref: "#/components/schemas/app-permissions"
           description: Test
       description: Returns a app permissions
+  /api/image/v1:
+    post:
+      operationId: createImage
+      tags:
+        - "image"
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateImageRequest"
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Value"
+          description: Test
+      description: Returns a create image request
 components:
   schemas:
     app-permissions:
@@ -58,3 +94,19 @@ components:
           type: array
           items:
             type: string
+    CreateImageRequest:
+      type: object
+      properties:
+        prompt:
+          description: A text description of the desired image(s). The maximum length is 1000 characters for `dall-e-2` and 4000 characters for `dall-e-3`.
+          type: string
+          example: "A cute baby sea otter"
+        model:
+          anyOf:
+            - type: string
+            - type: string
+              enum: ["dall-e-2", "dall-e-3"]
+          x-oaiTypeLabel: string
+          example: "dall-e-3"
+          nullable: true
+          description: The model to use for image generation.