diff --git a/src/Kiota.Builder/Extensions/StringExtensions.cs b/src/Kiota.Builder/Extensions/StringExtensions.cs
index 3d3a295114..45f5e561a8 100644
--- a/src/Kiota.Builder/Extensions/StringExtensions.cs
+++ b/src/Kiota.Builder/Extensions/StringExtensions.cs
@@ -20,6 +20,30 @@ public static string ToFirstCharacterLowerCase(this string? input)
public static string ToFirstCharacterUpperCase(this string? input)
=> string.IsNullOrEmpty(input) ? string.Empty : char.ToUpperInvariant(input[0]) + input[1..];
+ ///
+ /// Normalizes PascalCase names by lowering consecutive uppercase acronyms so that only
+ /// the first letter of each acronym remains uppercase (e.g., "ComplianceDLPApplications" becomes
+ /// "ComplianceDlpApplications"). When the last uppercase letter in a run is followed by a
+ /// lowercase letter, it is kept uppercase because it starts a new word.
+ ///
+ public static string NormalizePascalCaseAcronyms(this string? input)
+ {
+ if (string.IsNullOrEmpty(input) || input.Length < 2) return input ?? string.Empty;
+
+ var result = input.ToCharArray();
+ for (var i = 1; i < input.Length; i++)
+ {
+ if (char.IsUpper(input[i]) && char.IsUpper(input[i - 1]))
+ {
+ // Keep uppercase if this is the last uppercase letter before a lowercase letter (new word start)
+ if (i + 1 < input.Length && char.IsLower(input[i + 1]))
+ continue;
+ result[i] = char.ToLowerInvariant(input[i]);
+ }
+ }
+ return new string(result);
+ }
+
private static readonly char[] defaultSeparators = ['-'];
///
/// Converts a string delimited by a symbol to camel case, conserving the casing for the first character
diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs
index 123a2e8fc6..bf09a2ce63 100644
--- a/src/Kiota.Builder/Refiners/JavaRefiner.cs
+++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs
@@ -60,6 +60,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken
else
return s;
});
+ NormalizeAcronymCasing(generatedCode);
RemoveClassNamePrefixFromNestedClasses(generatedCode);
InsertOverrideMethodForRequestExecutorsAndBuildersAndConstructors(generatedCode);
ReplaceIndexersByMethodsWithParameter(generatedCode,
@@ -552,4 +553,27 @@ private void AddQueryParameterExtractorMethod(CodeElement currentElement, string
}
CrawlTree(currentElement, x => AddQueryParameterExtractorMethod(x, methodName));
}
+ ///
+ /// Normalizes acronym casing in class and enum names to prevent file name mismatches
+ /// when upstream metadata changes acronym casing (e.g., powerBi → powerBI).
+ /// Unlike CorrectNames, this allows case-only renames since InnerChildElements uses OrdinalIgnoreCase.
+ ///
+ private static void NormalizeAcronymCasing(CodeElement current)
+ {
+ if (current is CodeClass currentClass &&
+ currentClass.Name.NormalizePascalCaseAcronyms() is string refinedClassName &&
+ !currentClass.Name.Equals(refinedClassName, StringComparison.Ordinal) &&
+ currentClass.Parent is IBlock classParentBlock)
+ {
+ classParentBlock.RenameChildElement(currentClass.Name, refinedClassName);
+ }
+ else if (current is CodeEnum currentEnum &&
+ currentEnum.Name.NormalizePascalCaseAcronyms() is string refinedEnumName &&
+ !currentEnum.Name.Equals(refinedEnumName, StringComparison.Ordinal) &&
+ currentEnum.Parent is IBlock enumParentBlock)
+ {
+ enumParentBlock.RenameChildElement(currentEnum.Name, refinedEnumName);
+ }
+ CrawlTree(current, NormalizeAcronymCasing);
+ }
}
diff --git a/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs b/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs
index 9a1197998d..3a47d89471 100644
--- a/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs
+++ b/tests/Kiota.Builder.Tests/Extensions/StringExtensionsTests.cs
@@ -47,6 +47,25 @@ public void ToPascalCase()
Assert.Equal("Toto", "toto".ToPascalCase());
Assert.Equal("TotoPascalCase", "toto-pascal-case".ToPascalCase());
}
+ [Theory]
+ [InlineData(null, "")]
+ [InlineData("", "")]
+ [InlineData("A", "A")]
+ [InlineData("Ab", "Ab")]
+ [InlineData("ComplianceDLPApplicationsAuditRecord", "ComplianceDlpApplicationsAuditRecord")]
+ [InlineData("PowerBIAuditRecord", "PowerBiAuditRecord")]
+ [InlineData("PowerBIDlpAuditRecord", "PowerBiDlpAuditRecord")]
+ [InlineData("OnPremisesSharePointScannerDLPAuditRecord", "OnPremisesSharePointScannerDlpAuditRecord")]
+ [InlineData("ComplianceDLPExchangeClassificationCdpRecord", "ComplianceDlpExchangeClassificationCdpRecord")]
+ [InlineData("XMLHTTPRequest", "XmlhttpRequest")]
+ [InlineData("AuditRecord", "AuditRecord")]
+ [InlineData("somemodel", "somemodel")]
+ [InlineData("ABC", "Abc")]
+ [InlineData("ABCDef", "AbcDef")]
+ public void NormalizePascalCaseAcronyms(string input, string expected)
+ {
+ Assert.Equal(expected, input.NormalizePascalCaseAcronyms());
+ }
[Fact]
public void ToPascalCaseCustomSeparator()
{
diff --git a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
index 938adf9783..3571186d1a 100644
--- a/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
+++ b/tests/Kiota.Builder.Tests/Refiners/JavaLanguageRefinerTests.cs
@@ -616,6 +616,33 @@ public async Task ProduceCorrectNamesAsync()
await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root, cancellationToken: TestContext.Current.CancellationToken);
Assert.True(string.IsNullOrEmpty(model.Properties.First(static x => "custom".Equals(x.Name))!.NamePrefix));
}
+ [Theory]
+ [InlineData("ComplianceDLPApplicationsAuditRecord", "ComplianceDlpApplicationsAuditRecord")]
+ [InlineData("PowerBIAuditRecord", "PowerBiAuditRecord")]
+ [InlineData("OnPremisesSharePointScannerDLPAuditRecord", "OnPremisesSharePointScannerDlpAuditRecord")]
+ [InlineData("SimpleModel", "SimpleModel")]
+ public async Task NormalizesModelClassAcronymCasingAsync(string originalName, string expectedName)
+ {
+ var model = root.AddClass(new CodeClass
+ {
+ Name = originalName,
+ Kind = CodeClassKind.Model
+ }).First();
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root, cancellationToken: TestContext.Current.CancellationToken);
+ Assert.Equal(expectedName, model.Name);
+ }
+ [Theory]
+ [InlineData("AuditLogRecordDLP", "AuditLogRecordDlp")]
+ [InlineData("SimpleEnum", "SimpleEnum")]
+ public async Task NormalizesEnumAcronymCasingAsync(string originalName, string expectedName)
+ {
+ var model = root.AddEnum(new CodeEnum
+ {
+ Name = originalName,
+ }).First();
+ await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.Java }, root, cancellationToken: TestContext.Current.CancellationToken);
+ Assert.Equal(expectedName, model.Name);
+ }
[Fact]
public async Task AddsMethodsOverloadsAsync()
{