From 3115347514205b8488a2fc1716781fd982155123 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Sat, 14 Nov 2026 09:44:42 +0000 Subject: [PATCH 1/4] Test that every case change names its locale A check, run on every pull request, fails on any toLowerCase() or toUpperCase() with no argument in a Java file under src/main, and two tests set a Turkish default locale, where a capital I lowers to a dotless i. The JSON builder must still write ipdata and ismobile, and the Device Detection cloud engine must still read DeviceId from the cloud's deviceid. --- .github/workflows/root-locale.yml | 30 ++++++ ci/check-root-locale.Tests.ps1 | 98 +++++++++++++++++++ ci/check-root-locale.ps1 | 67 +++++++++++++ .../MissingPropertyHandlingTests.java | 54 ++++++++++ .../jsonbuilder/JsonBuilderTests.java | 29 ++++++ 5 files changed, 278 insertions(+) create mode 100644 .github/workflows/root-locale.yml create mode 100644 ci/check-root-locale.Tests.ps1 create mode 100644 ci/check-root-locale.ps1 diff --git a/.github/workflows/root-locale.yml b/.github/workflows/root-locale.yml new file mode 100644 index 000000000..ac8954fc9 --- /dev/null +++ b/.github/workflows/root-locale.yml @@ -0,0 +1,30 @@ +name: Root locale + +on: + pull_request: + push: + branches: + - main + - rc + workflow_dispatch: + +permissions: + contents: read + +jobs: + root-locale: + name: Every case change in the main code names its locale + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Test the check + shell: pwsh + run: Invoke-Pester -Path ./ci/check-root-locale.Tests.ps1 -CI + + - name: Check the case changes + shell: pwsh + run: ./ci/check-root-locale.ps1 diff --git a/ci/check-root-locale.Tests.ps1 b/ci/check-root-locale.Tests.ps1 new file mode 100644 index 000000000..1d1292be8 --- /dev/null +++ b/ci/check-root-locale.Tests.ps1 @@ -0,0 +1,98 @@ +# Tests for ci/check-root-locale.ps1. + +BeforeAll { + $script:Check = Join-Path $PSScriptRoot 'check-root-locale.ps1' + . $script:Check +} + +Describe 'Find-DefaultLocaleCaseChange' { + + It 'finds ' -ForEach @( + @{ Line = 'String key = property.getName().toLowerCase();' } + @{ Line = 'if (name.toUpperCase().equals(other)) {' } + @{ Line = 'key.toLowerCase( ).contains("x")' } + ) { + Find-DefaultLocaleCaseChange -Path 'A.java' -Text $Line | + Should -Be "A.java:1: $Line" + } + + It 'leaves alone' -ForEach @( + @{ Line = 'String key = property.getName().toLowerCase(Locale.ROOT);' } + @{ Line = 'name.toUpperCase(Locale.ENGLISH)' } + @{ Line = 'a.equalsIgnoreCase(b)' } + @{ Line = 'Character.toLowerCase(c)' } + ) { + Find-DefaultLocaleCaseChange -Path 'A.java' -Text $Line | + Should -BeNullOrEmpty + } + + It 'gives each line number' { + $text = "class A {`n String a = b.toLowerCase();`r`n String c = d;`n" + + " String e = f.toUpperCase();`n}" + Find-DefaultLocaleCaseChange -Path 'p/A.java' -Text $text | + Should -Be @( + 'p/A.java:2: String a = b.toLowerCase();' + 'p/A.java:4: String e = f.toUpperCase();') + } + + It 'finds nothing in an empty file' { + Find-DefaultLocaleCaseChange -Path 'A.java' -Text '' | + Should -BeNullOrEmpty + } +} + +Describe 'Test-MainSource' { + + It 'reads ' -ForEach @( + @{ Path = 'pipeline/pipeline.core/src/main/java/a/B.java' } + @{ Path = 'src/main/java/B.java' } + @{ Path = 'examples/device-detection/shared/src/main/java/a/B.java' } + ) { + Test-MainSource $Path | Should -BeTrue + } + + It 'leaves out ' -ForEach @( + @{ Path = 'pipeline/pipeline.core/src/test/java/a/B.java' } + @{ Path = 'pipeline/pipeline.core/src/main/resources/a.json' } + @{ Path = 'ci/check-root-locale.ps1' } + @{ Path = 'docs/src/main.java.md' } + ) { + Test-MainSource $Path | Should -BeFalse + } +} + +Describe 'The check' { + + BeforeAll { + $script:Repo = Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid()) + New-Item -ItemType Directory -Path $script:Repo | Out-Null + & git -C $script:Repo init -q + $main = Join-Path $script:Repo 'm/src/main/java' + $test = Join-Path $script:Repo 'm/src/test/java' + New-Item -ItemType Directory -Path $main, $test | Out-Null + Set-Content -LiteralPath (Join-Path $test 'T.java') ` + -Value 'class T { String a = b.toLowerCase(); }' + Set-Content -LiteralPath (Join-Path $main 'M.java') ` + -Value 'class M { String a = b.toLowerCase(Locale.ROOT); }' + & git -C $script:Repo add -A + } + + AfterAll { + Remove-Item -LiteralPath $script:Repo -Recurse -Force + } + + It 'passes when every main case change names its locale' { + $output = & pwsh -NoProfile -File $script:Check -RepoRoot $script:Repo + $LASTEXITCODE | Should -Be 0 + ($output -join "`n") | Should -Match 'Every case change in 1 main' + } + + It 'fails on a main case change with no locale, naming the line' { + Set-Content -LiteralPath (Join-Path $script:Repo 'm/src/main/java/N.java') ` + -Value "class N {`n String a = b.toUpperCase();`n}" + & git -C $script:Repo add -A + $output = & pwsh -NoProfile -File $script:Check -RepoRoot $script:Repo + $LASTEXITCODE | Should -Be 1 + $output | Should -Contain 'm/src/main/java/N.java:2: String a = b.toUpperCase();' + } +} diff --git a/ci/check-root-locale.ps1 b/ci/check-root-locale.ps1 new file mode 100644 index 000000000..2713cc958 --- /dev/null +++ b/ci/check-root-locale.ps1 @@ -0,0 +1,67 @@ +# Every case change in the main code names its locale. Java's toLowerCase() +# and toUpperCase() with no argument use the default locale, and under a +# Turkish or Azeri default a capital I lowers to a dotless i, so a name that +# holds one no longer matches its lower case form. This reads every Java file +# git tracks under a src/main folder, which leaves out the tests, build output +# and the submodules, and fails on any call that names no locale. Pass +# Locale.ROOT. +# +# Dot-sourcing the script defines Find-DefaultLocaleCaseChange and +# Test-MainSource without reading the repository, which is how its tests use +# them. + +[CmdletBinding()] +param( + # The root of the checkout to read. The default is the repository this + # script lives in. + [string]$RepoRoot = (Join-Path $PSScriptRoot '..') +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$script:DefaultLocalePattern = [regex]::new('\.to(?:Lower|Upper)Case\(\s*\)') + +function Find-DefaultLocaleCaseChange { + # One ":: " for each line of the text with a case + # change that names no locale. + param( + [Parameter(Mandatory)][string]$Path, + [Parameter(Mandatory)][AllowEmptyString()][string]$Text + ) + $lines = $Text -split "`r?`n" + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($script:DefaultLocalePattern.IsMatch($lines[$i])) { + "${Path}:$($i + 1): $($lines[$i].Trim())" + } + } +} + +function Test-MainSource { + # Whether a path as git gives it is a Java file under a src/main folder. + param([Parameter(Mandatory)][string]$Path) + $Path.EndsWith('.java') -and "/$Path".Contains('/src/main/') +} + +if ($MyInvocation.InvocationName -eq '.') { + return +} + +$root = (Resolve-Path $RepoRoot).Path +$tracked = & git -C $root -c core.quotePath=false ls-files +if ($LASTEXITCODE -ne 0) { + throw "git ls-files failed in $root" +} +$files = @($tracked | Where-Object { Test-MainSource $_ }) +$found = @(foreach ($file in $files) { + $text = [string](Get-Content -Raw -LiteralPath (Join-Path $root $file)) + Find-DefaultLocaleCaseChange -Path $file -Text $text +}) +if ($found.Count -gt 0) { + $found | ForEach-Object { Write-Output $_ } + Write-Output ("$($found.Count) case changes name no locale. Pass " + + 'Locale.ROOT, because the default locale can lower a capital I ' + + 'to a dotless i.') + exit 1 +} +Write-Output "Every case change in $($files.Count) main source files names its locale." diff --git a/device-detection/device-detection.cloud/src/test/java/fiftyone/devicedetection/cloud/flowelements/MissingPropertyHandlingTests.java b/device-detection/device-detection.cloud/src/test/java/fiftyone/devicedetection/cloud/flowelements/MissingPropertyHandlingTests.java index 8e85a1ca0..60ec1c840 100644 --- a/device-detection/device-detection.cloud/src/test/java/fiftyone/devicedetection/cloud/flowelements/MissingPropertyHandlingTests.java +++ b/device-detection/device-detection.cloud/src/test/java/fiftyone/devicedetection/cloud/flowelements/MissingPropertyHandlingTests.java @@ -39,6 +39,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; @@ -129,6 +130,59 @@ public void PropertyNotInResource() throws Exception { } } + /** + * A property whose name holds a capital I is read from the cloud's lower + * case key under a Turkish default locale, which would otherwise lower + * the I to a dotless i and look for a key the response does not have. + * + * @throws Exception + */ + @Test + public void PropertyWithCapitalI_TurkishLocale_HasValue() throws Exception { + Locale original = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try (DeviceDetectionCloudEngine engine = + new DeviceDetectionCloudEngineBuilder(loggerFactory) + .build()) { + + FlowData flowData = mock(FlowData.class); + addResponse(flowData, deviceIdJson); + DeviceDataCloud device = new DeviceDataCloudInternal( + loggerFactory.getLogger(DeviceDataCloud.class.getSimpleName()), + flowData, + engine, + mock(MissingPropertyService.class)); + engine.cloudRequestEngine = mock(CloudRequestEngine.class); + engine.aspectProperties = deviceIdProperties; + + engine.processEngine(flowData, device); + + AspectPropertyValue deviceId = device.getDeviceId(); + assertTrue(deviceId.hasValue()); + assertEquals(expectedDeviceId, deviceId.getValue()); + } finally { + Locale.setDefault(original); + } + } + + private static String expectedDeviceId = "12280-48866-24305-18092"; + + private static String deviceIdJson = + "{\n" + + " 'device': {\n" + + " 'deviceid': '" + expectedDeviceId + "'\n" + + " },\n" + + " 'javascriptProperties': []\n" + + "}\n"; + + private static List deviceIdProperties = + new ArrayList() { + private static final long serialVersionUID = 1L; + { + add(new AspectPropertyMetaDataDefault("DeviceId", null, null, String.class, null, true)); + } + }; + private static String expectedNullReason = "this is the null reason"; private static String expectedMissingPropertyReason = "this is the missing reason"; diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderTests.java index b81cf6c81..e7c3abba8 100644 --- a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderTests.java +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderTests.java @@ -203,6 +203,35 @@ public void JsonBuilder_ValidJson() throws Exception { assertTrue(isExpectedJson(json)); } + /** + * Element and property names are lower cased in the root locale, so a + * capital I keeps its dot under a Turkish default locale, which would + * otherwise lower it to a dotless i that the client script never reads. + */ + @Test + public void JsonBuilder_TurkishLocale_NamesLowerCasedInRootLocale() + throws Exception { + Map values = new HashMap<>(); + values.put("IsMobile", "True"); + ElementData element = mock(ElementData.class); + doReturn(values).when(element).asKeyMap(); + Map data = new HashMap<>(); + data.put("IpData", element); + + Locale original = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + String json; + try { + json = testIteration(1, data); + } finally { + Locale.setDefault(original); + } + + JSONObject obj = new JSONObject(json); + assertTrue(obj.has("ipdata"), json); + assertEquals("True", obj.getJSONObject("ipdata").opt("ismobile"), json); + } + /** * Check that the JSON element removes JavaScript properties from the * response after max number of iterations has been reached. From d3990945623c070357c78e3e366123ee03cf4a2b Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Sat, 14 Nov 2026 09:45:58 +0000 Subject: [PATCH 2/4] Change case in the root locale everywhere in the main code Every toLowerCase() and toUpperCase() under src/main now passes Locale.ROOT, 76 calls in 21 files. Under a Turkish or Azeri default locale a capital I lowered with the default locale becomes a dotless i, so the JSON builder wrote keys the client script never reads, the cloud engines looked for keys the response does not have, and names in the Pipeline builder and the usage sharing filter stopped matching. EvidenceKeyFilterShareUsage gets the same four changes as the usage sharing options change, line for line. --- .../DeviceDetectionCloudEngine.java | 13 +++--- .../shared/DeviceDataBaseOnPremise.java | 3 +- .../shared/testhelpers/KeyUtils.java | 3 +- .../examples/console/UpdateDataFile.java | 3 +- .../examples/shared/KeyHelper.java | 2 +- .../examples/console/UpdateDataFile.java | 3 +- .../examples/shared/KeyHelper.java | 2 +- .../IPIntelligenceCloudEngine.java | 25 +++++------ .../IPIntelligenceDataBaseOnPremise.java | 3 +- .../shared/testhelpers/KeyUtils.java | 3 +- .../flowelements/GeoLocationCloudEngine.java | 2 +- .../flowelements/CloudAspectEngineBase.java | 7 ++-- .../core/flowelements/FlowDataDefault.java | 2 +- .../core/flowelements/PipelineBuilder.java | 22 +++++----- .../data/EvidenceKeyFilterShareUsage.java | 9 ++-- .../flowelements/SetHeadersElement.java | 3 +- .../flowelements/interop/LibLoader.java | 5 ++- .../pipeline/engines/data/JsonLoader.java | 5 ++- .../flowelements/JsonBuilderElement.java | 42 +++++++++---------- .../flowelements/TranslationEngineBase.java | 3 +- .../fiftyone/pipeline/web/StartupHelpers.java | 4 +- 21 files changed, 90 insertions(+), 74 deletions(-) diff --git a/device-detection/device-detection.cloud/src/main/java/fiftyone/devicedetection/cloud/flowelements/DeviceDetectionCloudEngine.java b/device-detection/device-detection.cloud/src/main/java/fiftyone/devicedetection/cloud/flowelements/DeviceDetectionCloudEngine.java index 400ca1dad..069dba83d 100644 --- a/device-detection/device-detection.cloud/src/main/java/fiftyone/devicedetection/cloud/flowelements/DeviceDetectionCloudEngine.java +++ b/device-detection/device-detection.cloud/src/main/java/fiftyone/devicedetection/cloud/flowelements/DeviceDetectionCloudEngine.java @@ -43,6 +43,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TreeMap; @@ -272,7 +273,7 @@ private boolean loadAspectProperties(CloudRequestEngine engine) { private AspectPropertyValue getIntegerAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue intValue = new AspectPropertyValueDefault<>(); if(deviceObj.isNull(key)){ intValue.setNoValueMessage(getNoValueReason(deviceObj, key)); @@ -284,7 +285,7 @@ private AspectPropertyValue getIntegerAspectPropertyValue( } private AspectPropertyValue getDoubleAspectPropertyValue(JSONObject deviceObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue doubleValue = new AspectPropertyValueDefault<>(); if(deviceObj.isNull(key)){ doubleValue.setNoValueMessage(getNoValueReason(deviceObj, key)); @@ -306,7 +307,7 @@ private AspectPropertyValue getDoubleAspectPropertyValue(JSONObject devi private AspectPropertyValue getBooleanAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue booleanValue = new AspectPropertyValueDefault<>(); if(deviceObj.isNull(key)){ booleanValue.setNoValueMessage(getNoValueReason(deviceObj, key)); @@ -328,7 +329,7 @@ private AspectPropertyValue getBooleanAspectPropertyValue( private AspectPropertyValue> getListAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue> listValue = new AspectPropertyValueDefault<>(); if(deviceObj.isNull(key)) { @@ -357,7 +358,7 @@ private AspectPropertyValue> getListAspectPropertyValue( private AspectPropertyValue getJavaScriptAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue jsValue = new AspectPropertyValueDefault<>(); if(deviceObj.isNull(key)){ jsValue.setNoValueMessage(getNoValueReason(deviceObj, key)); @@ -378,7 +379,7 @@ private AspectPropertyValue getJavaScriptAspectPropertyValue( private AspectPropertyValue getStringAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue stringValue = new AspectPropertyValueDefault<>(); if(deviceObj.isNull(key)){ stringValue.setNoValueMessage(getNoValueReason(deviceObj, key)); diff --git a/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/DeviceDataBaseOnPremise.java b/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/DeviceDataBaseOnPremise.java index fcf900ce6..09b7c01f1 100644 --- a/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/DeviceDataBaseOnPremise.java +++ b/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/DeviceDataBaseOnPremise.java @@ -34,6 +34,7 @@ import org.slf4j.Logger; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -147,7 +148,7 @@ public Map asKeyMap() { for (ElementPropertyMetaData property : getPipeline() .getElementAvailableProperties() .get(getEngines().get(0).getElementDataKey()).values()) { - map.put(property.getName().toLowerCase(), + map.put(property.getName().toLowerCase(Locale.ROOT), getAs(property.getName(), AspectPropertyValue.class, property.getType())); diff --git a/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/testhelpers/KeyUtils.java b/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/testhelpers/KeyUtils.java index fdc8d2d6d..823df06a9 100644 --- a/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/testhelpers/KeyUtils.java +++ b/device-detection/device-detection.shared/src/main/java/fiftyone/devicedetection/shared/testhelpers/KeyUtils.java @@ -23,6 +23,7 @@ package fiftyone.devicedetection.shared.testhelpers; import java.util.Base64; +import java.util.Locale; import java.util.Objects; /** @@ -37,7 +38,7 @@ public class KeyUtils { public static String getNamedKey(String keyName) { String resourceKey = System.getenv(keyName); if (Objects.isNull(resourceKey)) { - resourceKey = System.getenv(keyName.toUpperCase()); + resourceKey = System.getenv(keyName.toUpperCase(Locale.ROOT)); if (Objects.isNull(resourceKey)) { resourceKey = System.getProperty(keyName); } diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java index ebffdef86..8d0b269d3 100644 --- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java +++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java @@ -37,6 +37,7 @@ import java.io.PrintWriter; import java.nio.file.*; import java.text.SimpleDateFormat; +import java.util.Locale; import java.util.Objects; import java.util.Scanner; @@ -277,7 +278,7 @@ static void run(String dataFilename, String licenseKey, boolean interactive) thr "in your licensed allocation."); System.out.print("Do you wish to continue with this example (y)? "); String input = scanner.nextLine(); - if ((input.isEmpty() || input.toLowerCase().startsWith("y")) == false) { + if ((input.isEmpty() || input.toLowerCase(Locale.ROOT).startsWith("y")) == false) { logger.info("Stopping example without download"); return; } diff --git a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java index 467a443be..f223dc325 100644 --- a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java +++ b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java @@ -139,7 +139,7 @@ private static String productList(String... products) { private static String readKey(String name) { for (String value : new String[]{ System.getenv(name), - System.getenv(name.toUpperCase()), + System.getenv(name.toUpperCase(Locale.ROOT)), System.getProperty(name)}) { if (KeyUtils.isInvalidKey(value) == false) { return value; diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java index a57a9b3e0..3ada68d96 100644 --- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java +++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java @@ -38,6 +38,7 @@ import java.io.PrintWriter; import java.nio.file.*; import java.text.SimpleDateFormat; +import java.util.Locale; import java.util.Objects; import java.util.Scanner; @@ -266,7 +267,7 @@ static void run(String dataFilename, String licenseKey, boolean interactive) thr "in your licensed allocation."); System.out.print("Do you wish to continue with this example (y)? "); String input = scanner.nextLine(); - if ((input.isEmpty() || input.toLowerCase().startsWith("y")) == false) { + if ((input.isEmpty() || input.toLowerCase(Locale.ROOT).startsWith("y")) == false) { logger.info("Stopping example without download"); return; } diff --git a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java index 2342f4047..8afc8c996 100644 --- a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java +++ b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java @@ -154,7 +154,7 @@ private static String productList(String... products) { private static String readKey(String name) { for (String value : new String[]{ System.getenv(name), - System.getenv(name.toUpperCase()), + System.getenv(name.toUpperCase(Locale.ROOT)), System.getProperty(name)}) { if (KeyUtils.isInvalidKey(value) == false) { return value; diff --git a/ip-intelligence/ip-intelligence.cloud/src/main/java/fiftyone/ipintelligence/cloud/flowelements/IPIntelligenceCloudEngine.java b/ip-intelligence/ip-intelligence.cloud/src/main/java/fiftyone/ipintelligence/cloud/flowelements/IPIntelligenceCloudEngine.java index 99b84cbb3..88f8fded4 100644 --- a/ip-intelligence/ip-intelligence.cloud/src/main/java/fiftyone/ipintelligence/cloud/flowelements/IPIntelligenceCloudEngine.java +++ b/ip-intelligence/ip-intelligence.cloud/src/main/java/fiftyone/ipintelligence/cloud/flowelements/IPIntelligenceCloudEngine.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -159,7 +160,7 @@ protected void processEngine(FlowData data, IPIntelligenceDataCloud aspectData) for (AspectPropertyMetaData property : getProperties()) { if (weightedPropertyNames.contains( - property.getName().toLowerCase())) { + property.getName().toLowerCase(Locale.ROOT))) { propertyMap.put( property.getName(), getWeightedListAspectPropertyValue(ipiObj, property)); @@ -280,7 +281,7 @@ private boolean loadAspectProperties(CloudRequestEngine engine) { for (AccessiblePropertyMetaData.PropertyMetaData item : product.properties) { if (item.type != null && item.type.startsWith("Weighted")) { - weightedPropertyNames.add(item.name.toLowerCase()); + weightedPropertyNames.add(item.name.toLowerCase(Locale.ROOT)); } AspectPropertyMetaData property = new AspectPropertyMetaDataDefault( item.name, @@ -355,7 +356,7 @@ private Class resolvePropertyType( private AspectPropertyValue getIntegerAspectPropertyValue( JSONObject ipiObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue intValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ intValue.setNoValueMessage(getNoValueReason(ipiObj, key)); @@ -367,7 +368,7 @@ private AspectPropertyValue getIntegerAspectPropertyValue( } private AspectPropertyValue getDoubleAspectPropertyValue(JSONObject ipiObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue doubleValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ doubleValue.setNoValueMessage(getNoValueReason(ipiObj, key)); @@ -388,7 +389,7 @@ private AspectPropertyValue getDoubleAspectPropertyValue(JSONObject ipiO * for the value not being present */ private AspectPropertyValue getFloatAspectPropertyValue(JSONObject ipiObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue floatValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ floatValue.setNoValueMessage(getNoValueReason(ipiObj, key)); @@ -410,7 +411,7 @@ private AspectPropertyValue getFloatAspectPropertyValue(JSONObject ipiObj private AspectPropertyValue getBooleanAspectPropertyValue( JSONObject ipiObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue booleanValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ booleanValue.setNoValueMessage(getNoValueReason(ipiObj, key)); @@ -432,7 +433,7 @@ private AspectPropertyValue getBooleanAspectPropertyValue( private AspectPropertyValue> getListAspectPropertyValue( JSONObject ipiObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue> listValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)) { @@ -465,7 +466,7 @@ private AspectPropertyValue> getListAspectPropertyValue( getWeightedListAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue>> weightedValue = new AspectPropertyValueDefault<>(); if (deviceObj.isNull(key)) { @@ -500,7 +501,7 @@ private AspectPropertyValue> getListAspectPropertyValue( private AspectPropertyValue getInetAddressAspectPropertyValue( JSONObject deviceObj, AspectPropertyMetaData property) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue ipValue = new AspectPropertyValueDefault<>(); if (deviceObj.isNull(key)) { ipValue.setNoValueMessage(getNoValueReason(deviceObj, key)); @@ -526,7 +527,7 @@ private AspectPropertyValue getInetAddressAspectPropertyValue( private AspectPropertyValue getJavaScriptAspectPropertyValue( JSONObject ipiObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue jsValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ jsValue.setNoValueMessage(getNoValueReason(ipiObj, key)); @@ -551,7 +552,7 @@ private AspectPropertyValue getJavaScriptAspectPropertyValue( private AspectPropertyValue getWktStringAspectPropertyValue( JSONObject ipiObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue wktValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ wktValue.setNoValueMessage(getNoValueReason(ipiObj, key)); @@ -579,7 +580,7 @@ private AspectPropertyValue getWktStringAspectPropertyValue( private AspectPropertyValue getStringAspectPropertyValue( JSONObject ipiObj, AspectPropertyMetaData property){ - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); AspectPropertyValue stringValue = new AspectPropertyValueDefault<>(); if(ipiObj.isNull(key)){ stringValue.setNoValueMessage(getNoValueReason(ipiObj, key)); diff --git a/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/IPIntelligenceDataBaseOnPremise.java b/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/IPIntelligenceDataBaseOnPremise.java index 32b2729e2..ef0b7e938 100644 --- a/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/IPIntelligenceDataBaseOnPremise.java +++ b/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/IPIntelligenceDataBaseOnPremise.java @@ -40,6 +40,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -229,7 +230,7 @@ public Map asKeyMap() { final Map keyedProps = allProps.get(key); final java.util.Collection relevantProps = keyedProps.values(); for (ElementPropertyMetaData property : relevantProps) { - map.put(property.getName().toLowerCase(), + map.put(property.getName().toLowerCase(Locale.ROOT), getAs(property.getName(), AspectPropertyValue.class, toWeightedListType(property.getType(), false))); diff --git a/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/testhelpers/KeyUtils.java b/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/testhelpers/KeyUtils.java index 75533a5ef..16aaf3bc9 100644 --- a/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/testhelpers/KeyUtils.java +++ b/ip-intelligence/ip-intelligence.shared/src/main/java/fiftyone/ipintelligence/shared/testhelpers/KeyUtils.java @@ -23,6 +23,7 @@ package fiftyone.ipintelligence.shared.testhelpers; import java.util.Base64; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -90,7 +91,7 @@ public static String getResourceKey(String legacyKeyName) { public static String getNamedKey(String keyName) { String resourceKey = System.getenv(keyName); if (Objects.isNull(resourceKey)) { - resourceKey = System.getenv(keyName.toUpperCase()); + resourceKey = System.getenv(keyName.toUpperCase(Locale.ROOT)); if (Objects.isNull(resourceKey)) { resourceKey = System.getProperty(keyName); } diff --git a/location/geo-location.cloud/src/main/java/fiftyone/geolocation/flowelements/GeoLocationCloudEngine.java b/location/geo-location.cloud/src/main/java/fiftyone/geolocation/flowelements/GeoLocationCloudEngine.java index 6c8eae436..eaa128e9a 100644 --- a/location/geo-location.cloud/src/main/java/fiftyone/geolocation/flowelements/GeoLocationCloudEngine.java +++ b/location/geo-location.cloud/src/main/java/fiftyone/geolocation/flowelements/GeoLocationCloudEngine.java @@ -143,7 +143,7 @@ protected void processEngine(FlowData data, CloudGeoData aspectData) { Map methodData = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (AspectPropertyMetaData property : getProperties()) { - String key = property.getName().toLowerCase(); + String key = property.getName().toLowerCase(Locale.ROOT); // A property the response leaves out has no entry, so // reading it throws a PropertyMissingException. if (geoObj.has(key) == false) { diff --git a/pipeline/pipeline.cloudrequestengine/src/main/java/fiftyone/pipeline/cloudrequestengine/flowelements/CloudAspectEngineBase.java b/pipeline/pipeline.cloudrequestengine/src/main/java/fiftyone/pipeline/cloudrequestengine/flowelements/CloudAspectEngineBase.java index 80b39e9a6..83d7b203c 100644 --- a/pipeline/pipeline.cloudrequestengine/src/main/java/fiftyone/pipeline/cloudrequestengine/flowelements/CloudAspectEngineBase.java +++ b/pipeline/pipeline.cloudrequestengine/src/main/java/fiftyone/pipeline/cloudrequestengine/flowelements/CloudAspectEngineBase.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -309,7 +310,7 @@ private Map buildMetaDataMap( List properties) { Map result = new HashMap<>(); for (ElementPropertyMetaData property : properties) { - result.put(property.getName().toLowerCase(), property); + result.put(property.getName().toLowerCase(Locale.ROOT), property); } return result; } @@ -346,9 +347,9 @@ protected Map createAPVMap( for (Map.Entry property : cloudData.entrySet()) { if (property.getKey().endsWith("nullreason") == false) { Object outputValue = property.getValue(); - if (metaDataMap.containsKey(property.getKey().toLowerCase())) { + if (metaDataMap.containsKey(property.getKey().toLowerCase(Locale.ROOT))) { ElementPropertyMetaData metaData = - metaDataMap.get(property.getKey().toLowerCase()); + metaDataMap.get(property.getKey().toLowerCase(Locale.ROOT)); // If this property has a type of AspectPropertyValue // then create a new instance and populate it. diff --git a/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/FlowDataDefault.java b/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/FlowDataDefault.java index f8758e126..bb094f75b 100644 --- a/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/FlowDataDefault.java +++ b/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/FlowDataDefault.java @@ -355,7 +355,7 @@ public Map getWhere(PropertyMatcher matcher) { map.put( element.getElementDataKey() + "." + property.getName(), get(element.getElementDataKey()) - .get(property.getName().toLowerCase()).toString()); + .get(property.getName().toLowerCase(Locale.ROOT)).toString()); } } } diff --git a/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/PipelineBuilder.java b/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/PipelineBuilder.java index f10d49d1c..cd72b9eb2 100644 --- a/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/PipelineBuilder.java +++ b/pipeline/pipeline.core/src/main/java/fiftyone/pipeline/core/flowelements/PipelineBuilder.java @@ -206,7 +206,7 @@ public static Set> getAvailableElementBuilders() { config.setInputsFilter(new Predicate() { @Override public boolean test(String s) { - return s.toLowerCase().contains("flowelements"); + return s.toLowerCase(Locale.ROOT).contains("flowelements"); } }); config.setExpandSuperTypes(false); @@ -676,7 +676,7 @@ private List processBuildParameters( if (methods.size() == 0) { // If not then add the parameter to the list of parameters // to pass to the Build method instead. - buildParameterList.add(parameter.getKey().toLowerCase()); + buildParameterList.add(parameter.getKey().toLowerCase(Locale.ROOT)); } else { boolean methodCalled = false; int counter = 0; @@ -802,7 +802,7 @@ private List getMethods( Method[] methods) { int tries = 0; List matchingMethods = new ArrayList<>(); - String lowerMethodName = methodName.toLowerCase(); + String lowerMethodName = methodName.toLowerCase(Locale.ROOT); while (tries < 3 && matchingMethods.size() == 0) { List potentialMethods = null; @@ -812,7 +812,7 @@ private List getMethods( // First try and find a method that matches the // supplied name exactly. for (Method method : methods) { - if (method.getName().toLowerCase().equals(lowerMethodName)) { + if (method.getName().toLowerCase(Locale.ROOT).equals(lowerMethodName)) { matchingMethods.add(method); } } @@ -822,7 +822,7 @@ private List getMethods( // supplied name with 'set' added to the start. String tempName = "set" + lowerMethodName; for (Method method : methods) { - if (method.getName().toLowerCase().equals(tempName)) { + if (method.getName().toLowerCase(Locale.ROOT).equals(tempName)) { matchingMethods.add(method); } } @@ -835,10 +835,10 @@ private List getMethods( try { AlternateName alternateName = method.getAnnotation(AlternateName.class); - if (alternateName.value().toLowerCase().equals( + if (alternateName.value().toLowerCase(Locale.ROOT).equals( lowerMethodName)) { tempMethods.add(method); - } else if (alternateName.value().toLowerCase().equals( + } else if (alternateName.value().toLowerCase(Locale.ROOT).equals( "set" + lowerMethodName)) { tempMethods.add(method); } @@ -880,7 +880,7 @@ private List getMethods( private Class getBuilderType(String builderName) { int tries = 0; Class builderType = null; - String lowerBuilderName = builderName.toLowerCase(); + String lowerBuilderName = builderName.toLowerCase(Locale.ROOT); while (tries < 3 && builderType == null) { List> potentialBuilders = new ArrayList<>(); @@ -890,7 +890,7 @@ private Class getBuilderType(String builderName) { // First try and find a builder that matches the // supplied name exactly. for (Class builder : elementBuilders) { - if (builder.getSimpleName().toLowerCase().equals(lowerBuilderName)) { + if (builder.getSimpleName().toLowerCase(Locale.ROOT).equals(lowerBuilderName)) { potentialBuilders.add(builder); } } @@ -900,7 +900,7 @@ private Class getBuilderType(String builderName) { // Next, try and find a builder that matches the // supplied name with 'builder' added to the end. for (Class builder : elementBuilders) { - if (builder.getSimpleName().toLowerCase().equals(tempName)) { + if (builder.getSimpleName().toLowerCase(Locale.ROOT).equals(tempName)) { potentialBuilders.add(builder); } } @@ -912,7 +912,7 @@ private Class getBuilderType(String builderName) { for (Class builder : elementBuilders) { for (Annotation annotation : builder.getAnnotations()) { if (annotation instanceof ElementBuilder && - ((ElementBuilder) annotation).alternateName().toLowerCase().equals(lowerBuilderName)) { + ((ElementBuilder) annotation).alternateName().toLowerCase(Locale.ROOT).equals(lowerBuilderName)) { builders.add(builder); } } diff --git a/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/data/EvidenceKeyFilterShareUsage.java b/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/data/EvidenceKeyFilterShareUsage.java index 3bfd42e8f..e3360af33 100644 --- a/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/data/EvidenceKeyFilterShareUsage.java +++ b/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/data/EvidenceKeyFilterShareUsage.java @@ -29,6 +29,7 @@ import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import static fiftyone.pipeline.core.Constants.*; @@ -167,15 +168,15 @@ public EvidenceKeyFilterShareUsage( this.blockedHttpHeaders.add("cookie"); this.includeSession = includeSession; this.sessionCookieName = sessionCookieName == null ? - null : sessionCookieName.toLowerCase(); + null : sessionCookieName.toLowerCase(Locale.ROOT); for (String header : blockedHttpHeaders) { - String lowerHeader = header.toLowerCase(); + String lowerHeader = header.toLowerCase(Locale.ROOT); if (!this.blockedHttpHeaders.contains(lowerHeader)) { this.blockedHttpHeaders.add(lowerHeader); } } for (String parameter : includedQueryStringParams) { - String lowerParameter = parameter.toLowerCase(); + String lowerParameter = parameter.toLowerCase(Locale.ROOT); if (!this.includedQueryStringParams.contains(lowerParameter)) { this.includedQueryStringParams.add(lowerParameter); } @@ -189,7 +190,7 @@ public boolean include(String key) { if (ShareUsageEvidence.HTTP_REQUEST_KEY.equalsIgnoreCase(key)) { return false; } - String lowerKey = key.toLowerCase(); + String lowerKey = key.toLowerCase(Locale.ROOT); for (String neverShared : NEVER_SHARED_EVIDENCE_KEYS) { if (lowerKey.equals(neverShared) || lowerKey.endsWith(EVIDENCE_SEPERATOR + neverShared)) { diff --git a/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/SetHeadersElement.java b/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/SetHeadersElement.java index 9bb05c6ea..d464152a7 100644 --- a/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/SetHeadersElement.java +++ b/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/SetHeadersElement.java @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.Hashtable; import java.util.List; +import java.util.Locale; import java.util.Map; import org.slf4j.Logger; @@ -235,7 +236,7 @@ private PipelineConfig populateConfig(Pipeline pipeline) { PipelineConfig config = new PipelineConfig(); pipeline.getElementAvailableProperties().forEach((k, v) -> { v.forEach((p, m) -> { - if (p.toLowerCase() + if (p.toLowerCase(Locale.ROOT) .startsWith(SET_HEADER_PREFIX_LOWER_CASE)) { PropertyDetails details = new PropertyDetails(); details.propertyMetaData = m; diff --git a/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/interop/LibLoader.java b/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/interop/LibLoader.java index 1492a34ba..65c97e55b 100644 --- a/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/interop/LibLoader.java +++ b/pipeline/pipeline.engines.fiftyone/src/main/java/fiftyone/pipeline/engines/fiftyone/flowelements/interop/LibLoader.java @@ -28,6 +28,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Locale; import java.util.Optional; /** @@ -44,7 +45,7 @@ public class LibLoader { * @throws UnsupportedOperationException if the OS is not supported */ private static OS getOs() throws UnsupportedOperationException { - String os = System.getProperty("os.name").toLowerCase(); + String os = System.getProperty("os.name").toLowerCase(Locale.ROOT); // Check for Windows if (os.contains("win")) { @@ -76,7 +77,7 @@ private static OS getOs() throws UnsupportedOperationException { * supported */ private static String getArch() throws UnsupportedOperationException { - String arch = System.getProperty("os.arch").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(Locale.ROOT); // Check aarch64/arm64 BEFORE generic arm (arm64 contains "arm") if (arch.contains("aarch64") || arch.contains("arm64")) { diff --git a/pipeline/pipeline.engines/src/main/java/fiftyone/pipeline/engines/data/JsonLoader.java b/pipeline/pipeline.engines/src/main/java/fiftyone/pipeline/engines/data/JsonLoader.java index fc2ee0204..c4ebdf413 100644 --- a/pipeline/pipeline.engines/src/main/java/fiftyone/pipeline/engines/data/JsonLoader.java +++ b/pipeline/pipeline.engines/src/main/java/fiftyone/pipeline/engines/data/JsonLoader.java @@ -28,6 +28,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -54,12 +55,12 @@ private T loadData(Reader reader, Class type) throws IOException, IllegalAcce Map parameters = new HashMap<>(); for (String key : jsonObj.keySet()) { - parameters.put(key.toLowerCase(), jsonObj. get(key)); + parameters.put(key.toLowerCase(Locale.ROOT), jsonObj. get(key)); } for (Method method : type.getMethods()) { if (method.getName().startsWith("set")) { - String variableName = method.getName().substring("set".length()).toLowerCase(); + String variableName = method.getName().substring("set".length()).toLowerCase(Locale.ROOT); Class[] types = method.getParameterTypes(); if (types.length == 1 && parameters.containsKey(variableName)) { Object variable = parameters.get(variableName); diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java index 413113d6e..b8ed4ca3e 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java @@ -241,12 +241,12 @@ private Map getAllProperties( for (Map.Entry element : data.elementDataAsMap().entrySet()) { if (elementBlacklist.contains(element.getKey()) == false && - allProperties.containsKey(element.getKey().toLowerCase()) == false) { + allProperties.containsKey(element.getKey().toLowerCase(Locale.ROOT)) == false) { Map elementProperties = getValues( - element.getKey().toLowerCase(), + element.getKey().toLowerCase(Locale.ROOT), ((ElementData)element.getValue()).asKeyMap(), config); - allProperties.put(element.getKey().toLowerCase(), elementProperties); + allProperties.put(element.getKey().toLowerCase(Locale.ROOT), elementProperties); } } @@ -279,7 +279,7 @@ private Map getValues( throw new IllegalArgumentException("config"); } - dataPath = dataPath.toLowerCase(); + dataPath = dataPath.toLowerCase(Locale.ROOT); Map values = new HashMap<>(); for (Map.Entry value : sourceData.entrySet()) { Object propertyValue = null; @@ -291,8 +291,8 @@ private Map getValues( propertyValue = aspectProperty.getValue(); } else { - values.put(value.getKey().toLowerCase(), null); - values.put(value.getKey().toLowerCase() + "nullreason", + values.put(value.getKey().toLowerCase(Locale.ROOT), null); + values.put(value.getKey().toLowerCase(Locale.ROOT) + "nullreason", aspectProperty.getNoValueMessage()); } } @@ -302,7 +302,7 @@ private Map getValues( String completeName = dataPath + EVIDENCE_SEPERATOR + - value.getKey().toLowerCase(); + value.getKey().toLowerCase(Locale.ROOT); if (propertyValue != null) { // If the value is a list of complex types then @@ -316,7 +316,7 @@ private Map getValues( List results = new ArrayList<>(); for (Object elementData : elementDatas) { results.add(getValues( - dataPath + "." + value.getKey().toLowerCase(), + dataPath + "." + value.getKey().toLowerCase(Locale.ROOT), ((ElementData) elementData).asKeyMap(), config)); } @@ -329,11 +329,11 @@ else if (propertyValue instanceof List && } // Add this value to the output - values.put(value.getKey().toLowerCase(), propertyValue); + values.put(value.getKey().toLowerCase(Locale.ROOT), propertyValue); // Add 'delayexecution' flag if needed. if (config.delayedExecutionProperties.contains(completeName)) { - values.put(value.getKey().toLowerCase() + "delayexecution", true); + values.put(value.getKey().toLowerCase(Locale.ROOT) + "delayexecution", true); } } // Add evidence properties list if needed. @@ -341,7 +341,7 @@ else if (propertyValue instanceof List && if (config.delayedEvidenceProperties.containsKey(completeName)) { List evidenceProperties = config.delayedEvidenceProperties .get(completeName); - values.put(value.getKey().toLowerCase() + "evidenceproperties", evidenceProperties); + values.put(value.getKey().toLowerCase(Locale.ROOT) + "evidenceproperties", evidenceProperties); } } return values; @@ -392,7 +392,7 @@ private List getJavaScriptProperties( for (Object propertyObject : entry.entrySet()) { Map.Entry property = (Entry)propertyObject; props.add( - element.getKey().toLowerCase() + EVIDENCE_SEPERATOR + property.getKey().toString().toLowerCase()); + element.getKey().toLowerCase(Locale.ROOT) + EVIDENCE_SEPERATOR + property.getKey().toString().toLowerCase(Locale.ROOT)); } } } @@ -422,13 +422,13 @@ private List getJavaScriptProperties( keys = javascriptPropertiesMap.keySet().toArray(keys); for(String key : keys) { if(containsIgnoreCase(props, key) == false) { - javascriptPropertiesMap.remove(key.toLowerCase()); + javascriptPropertiesMap.remove(key.toLowerCase(Locale.ROOT)); } } List javascriptPropertyNames = new ArrayList<>(); for (String name : javascriptPropertiesMap.keySet()) { - javascriptPropertyNames.add(name.toLowerCase()); + javascriptPropertyNames.add(name.toLowerCase(Locale.ROOT)); } return javascriptPropertyNames; } @@ -460,7 +460,7 @@ private void addErrors(FlowData data, Map allProperties) { } static String resolveName(String name) { - return name == JAVASCRIPT_PROPERTIES_NAME ? name : name.toLowerCase(); + return name == JAVASCRIPT_PROPERTIES_NAME ? name : name.toLowerCase(Locale.ROOT); } private String buildJson(Map allProperties) { @@ -503,7 +503,7 @@ private PipelineConfig populateMetaDataCollections(Pipeline pipeline) { pipeline.getElementAvailableProperties().entrySet()) { for (String propertyName : getDelayedPropertyNames( - element.getKey().toLowerCase(), + element.getKey().toLowerCase(Locale.ROOT), element.getValue().values())) { config.delayedExecutionProperties.add(propertyName); } @@ -517,8 +517,8 @@ private PipelineConfig populateMetaDataCollections(Pipeline pipeline) { pipeline.getElementAvailableProperties().entrySet()) { for (Map.Entry> property : getEvidencePropertyNames( config.delayedExecutionProperties, - element.getKey().toLowerCase(), - element.getKey().toLowerCase(), + element.getKey().toLowerCase(Locale.ROOT), + element.getKey().toLowerCase(Locale.ROOT), element.getValue().values()).entrySet()) { config.delayedEvidenceProperties.put( property.getKey(), @@ -543,7 +543,7 @@ private List getDelayedPropertyNames ( property.getType().equals(JavaScript.class)) { result.add(dataPath + EVIDENCE_SEPERATOR + - property.getName().toLowerCase()); + property.getName().toLowerCase(Locale.ROOT)); } // Call recursively for any properties that have sub-properties. @@ -575,7 +575,7 @@ private Map> getEvidencePropertyNames( for (String evidenceProperty : property.getEvidenceProperties()) { String evidenceName = elementDataKey + EVIDENCE_SEPERATOR + - evidenceProperty.toLowerCase(); + evidenceProperty.toLowerCase(Locale.ROOT); if (delayedExecutionProperties.contains(evidenceName)) { evidenceProperties.add(evidenceName); } @@ -585,7 +585,7 @@ private Map> getEvidencePropertyNames( // more evidence properties. if (evidenceProperties.size() > 0) { result.put(propertyDataPath + EVIDENCE_SEPERATOR + - property.getName().toLowerCase(), + property.getName().toLowerCase(Locale.ROOT), evidenceProperties); } diff --git a/pipeline/pipeline.translation/src/main/java/fiftyone/pipeline/translation/flowelements/TranslationEngineBase.java b/pipeline/pipeline.translation/src/main/java/fiftyone/pipeline/translation/flowelements/TranslationEngineBase.java index 624c7b116..20176eb99 100644 --- a/pipeline/pipeline.translation/src/main/java/fiftyone/pipeline/translation/flowelements/TranslationEngineBase.java +++ b/pipeline/pipeline.translation/src/main/java/fiftyone/pipeline/translation/flowelements/TranslationEngineBase.java @@ -46,6 +46,7 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -206,7 +207,7 @@ public List getProperties() { Set seen = new LinkedHashSet<>(); for (TranslationProperty translation : translationProperties) { String name = translation.getDestinationProperty(); - if (seen.add(name.toLowerCase())) { + if (seen.add(name.toLowerCase(Locale.ROOT))) { result.add(new ElementPropertyMetaDataDefault( name, this, "", Object.class, true)); } diff --git a/pipeline/pipeline.web.packages/pipeline.web/src/main/java/fiftyone/pipeline/web/StartupHelpers.java b/pipeline/pipeline.web.packages/pipeline.web/src/main/java/fiftyone/pipeline/web/StartupHelpers.java index 7e13e8a2a..fb6921bd2 100644 --- a/pipeline/pipeline.web.packages/pipeline.web/src/main/java/fiftyone/pipeline/web/StartupHelpers.java +++ b/pipeline/pipeline.web.packages/pipeline.web/src/main/java/fiftyone/pipeline/web/StartupHelpers.java @@ -32,6 +32,8 @@ import fiftyone.pipeline.javascriptbuilder.flowelements.JavaScriptBuilderElement; import fiftyone.pipeline.jsonbuilder.flowelements.JsonBuilderElement; +import java.util.Locale; + /** * Static methods used on server startup to configure and build the Pipeline and * services needed. @@ -50,7 +52,7 @@ private static int getElementIndex( String name) { int index = 0; for (ElementOptions element : options.elements) { - if (element.builderName.toLowerCase().contains(name.toLowerCase())) { + if (element.builderName.toLowerCase(Locale.ROOT).contains(name.toLowerCase(Locale.ROOT))) { return index; } index++; From 576615ee4a4eaf85decbf162377deafd2c872bbb Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Sat, 14 Nov 2026 17:23:06 +0000 Subject: [PATCH 3/4] Test that the examples write numbers a machine reads in the root locale CsvNumbersTest expects the distance CompareOnPrem writes into its CSV and the average MetricsOnPrem writes into its CSV with a full stop under a German default locale. RandomIpTest expects the client IP addresses ShareUsageExample makes up to use the digits 0 to 9 under a Saudi Arabian default locale. --- .../examples/console/RandomIpTest.java | 53 +++++++++++++++++++ .../examples/console/CsvNumbersTest.java | 49 +++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/RandomIpTest.java create mode 100644 examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/CsvNumbersTest.java diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/RandomIpTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/RandomIpTest.java new file mode 100644 index 000000000..ab437c3c0 --- /dev/null +++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/RandomIpTest.java @@ -0,0 +1,53 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +package fiftyone.devicedetection.examples.console; + +import org.junit.Test; + +import java.util.Locale; + +import static org.junit.Assert.assertTrue; + +/** + * The client IP address ShareUsageExample makes up for each record is + * Evidence that an engine reads, so it is written with the digits 0 to 9 + * whatever the default locale. + */ +public class RandomIpTest { + + private static final int ADDRESSES = 20; + + @Test + public void randomIp_ArabicDefaultLocale_AsciiDigits() { + Locale saved = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("ar-SA")); + try { + for (int i = 0; i < ADDRESSES; i++) { + String ip = ShareUsageExample.generateRandomIp(); + assertTrue(ip, ip.matches("[0-9]{1,3}(\\.[0-9]{1,3}){3}")); + } + } finally { + Locale.setDefault(saved); + } + } +} diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/CsvNumbersTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/CsvNumbersTest.java new file mode 100644 index 000000000..90bb4e06b --- /dev/null +++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/CsvNumbersTest.java @@ -0,0 +1,49 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +package fiftyone.ipintelligence.examples.console; + +import org.junit.Test; + +import java.util.Locale; + +import static org.junit.Assert.assertEquals; + +/** + * The numbers CompareOnPrem and MetricsOnPrem write into their CSV files + * have a full stop as the decimal separator whatever the default locale, + * because a decimal comma would split one column into two. + */ +public class CsvNumbersTest { + + @Test + public void csvNumbers_GermanDefaultLocale_FullStop() { + Locale saved = Locale.getDefault(); + Locale.setDefault(Locale.GERMANY); + try { + assertEquals("12.346", CompareOnPrem.kilometres(12.3456)); + assertEquals("3.50", MetricsOnPrem.twoDecimalPlaces(3.5)); + } finally { + Locale.setDefault(saved); + } + } +} From d6263a263e9172afc55e3f70710d8fcb342b375b Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Sat, 14 Nov 2026 17:24:01 +0000 Subject: [PATCH 4/4] Write the numbers the examples give a machine in the root locale CompareOnPrem and MetricsOnPrem wrote a distance and an average into their CSV rows with String.format and the default locale, so a locale with a decimal comma split one column into two. ShareUsageExample made up client IP addresses the same way, so a locale whose digits are not 0 to 9 gave Evidence no engine can read. Each now formats in Locale.ROOT, and the two CSV numbers are package-private methods so a test can reach them. --- .../examples/console/ShareUsageExample.java | 5 +++-- .../examples/console/CompareOnPrem.java | 12 +++++++++++- .../examples/console/MetricsOnPrem.java | 12 +++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/ShareUsageExample.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/ShareUsageExample.java index 50d44c364..9510f2935 100644 --- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/ShareUsageExample.java +++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/ShareUsageExample.java @@ -37,6 +37,7 @@ import java.nio.file.Files; import java.util.HashMap; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.stream.Collectors; @@ -215,8 +216,8 @@ private static Map filterEvidence(Map evidence, * * @return a random IPv4 address string */ - private static String generateRandomIp() { - return String.format("%d.%d.%d.%d", + static String generateRandomIp() { + return String.format(Locale.ROOT, "%d.%d.%d.%d", random.nextInt(256), random.nextInt(256), random.nextInt(256), diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java index 0fc3c7bbf..002f9818f 100644 --- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java +++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java @@ -96,6 +96,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -421,13 +422,22 @@ private static String processTruth(Pipeline pipeline, Truth truth) latitude + "," + longitude + "," + (confidence != null ? confidence : "") + "," + - String.format("%.3f", distanceKms) + "," + + kilometres(distanceKms) + "," + area.getSquareKms() + "," + area.getGeometries() + "," + area.getContains(); } } + /** + * A distance to three decimal places with a full stop as the separator + * whatever the default locale, as a decimal comma would split the CSV + * column in two. + */ + static String kilometres(double distanceKms) { + return String.format(Locale.ROOT, "%.3f", distanceKms); + } + /** * Returns the great circle distance in kilometers between two points * using the haversine formula. diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java index 4730245ec..a4c2d3993 100644 --- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java +++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java @@ -78,6 +78,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Random; @@ -736,11 +737,20 @@ private static void writeCsv(PrintWriter output, Map groups) { metric.areaCount + "," + metric.getAverageAreaKm() + "," + metric.getEquivalentRadiusKm() + "," + - String.format("%.2f", metric.getAveragePolygons())); + twoDecimalPlaces(metric.getAveragePolygons())); } output.flush(); } + /** + * A number to two decimal places with a full stop as the separator + * whatever the default locale, as a decimal comma would split the CSV + * column in two. + */ + static String twoDecimalPlaces(double value) { + return String.format(Locale.ROOT, "%.2f", value); + } + /** * Quote a CSV field when it contains a comma or quote. */