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/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.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/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/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/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/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/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 06ae82900..d2021334c 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/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. */ 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/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); + } + } +} 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 687d1573d..f2e48297b 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 984fd29de..760d2bf0f 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/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.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. 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++;