From 208114b247e64368ffad43318f8b00061558324b Mon Sep 17 00:00:00 2001 From: wantaek Date: Thu, 13 Aug 2026 21:15:39 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20EgovStringUtil=EC=9D=98=20=EC=8B=9D?= =?UTF-8?q?=EB=B3=84=EC=9E=90=20=ED=91=9C=EA=B8=B0=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=EC=9D=B4=20=EB=A1=9C=EC=BC=80=EC=9D=BC=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EB=8B=A4=EB=A5=B8=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20?= =?UTF-8?q?=EB=82=B4=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit convertToCamelCase와 convertToUnderScore는 자기 javadoc이 식별자 변환임을 명시한다("This method convert 'string_util' to 'stringUtil'", "Convert a camel case string to underscore representation"). 그런데 인자 없는 toLowerCase()를 써서 JVM 기본 로케일을 따르므로, 터키어·아제르바이잔어 로케일에서 I가 점 없는 ı(U+0131)로 바뀐다. convertToCamelCase("PRINT_STATUS", '_') -> "prıntStatus" (기대 "printStatus") convertToUnderScore("printId") -> "print_ıd" (기대 "print_id") 같은 일을 하는 형제 구현이 이 저장소에 둘 있고 둘 다 로케일 독립이다 — fdl.security의 CamelCaseUtil.convert2CamelCase와 psl.dataaccess의 CamelUtil.convert2CamelCase가 Character.toUpperCase(char)/toLowerCase(char)를 쓴다. 같은 목적의 EgovStringUtil 메서드만 String 단위 변환을 거친다. 두 호출 지점에 Locale.ROOT를 명시했다. 같은 결함 클래스를 Locale.ROOT로 고친 선례가 이 저장소에 있다 — DefaultMapUserDetailsMapping의 컬럼명 소문자화(#321). 같은 파일의 capitalize와 swapFirstLetterCase도 인자 없는 변환을 쓰지만 범위에서 뺐다. 두 메서드는 식별자 용도라는 근거가 없다. swapFirstLetterCase는 javadoc 예시가 'Password'/'password'이고 기존 테스트도 그 단어만 다룬다. capitalize는 javadoc이 메서드명 한 줄뿐이고 저장소 안에 호출처도 테스트도 없다. 로케일을 고정하면 터키어 단어를 대문자화하려는 정상 사용을 오히려 깨뜨린다. --- .../rte/fdl/string/EgovStringUtil.java | 5 ++- .../fdl/string/EgovStringUtilLocaleTest.java | 44 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 Foundation/org.egovframe.rte.fdl.string/src/test/java/org/egovframe/rte/fdl/string/EgovStringUtilLocaleTest.java diff --git a/Foundation/org.egovframe.rte.fdl.string/src/main/java/org/egovframe/rte/fdl/string/EgovStringUtil.java b/Foundation/org.egovframe.rte.fdl.string/src/main/java/org/egovframe/rte/fdl/string/EgovStringUtil.java index 4e280948..6ec761be 100755 --- a/Foundation/org.egovframe.rte.fdl.string/src/main/java/org/egovframe/rte/fdl/string/EgovStringUtil.java +++ b/Foundation/org.egovframe.rte.fdl.string/src/main/java/org/egovframe/rte/fdl/string/EgovStringUtil.java @@ -25,6 +25,7 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -990,7 +991,7 @@ public static List getTokens(String lst) { public static String convertToCamelCase(String targetString, char posChar) { StringBuilder result = new StringBuilder(); boolean nextUpper = false; - String allLower = targetString.toLowerCase(); + String allLower = targetString.toLowerCase(Locale.ROOT); for (int i = 0; i < allLower.length(); i++) { char currentChar = allLower.charAt(i); if (currentChar == posChar) { @@ -1029,7 +1030,7 @@ public static String convertToUnderScore(String camelCase) { if (i > 0 && Character.isUpperCase(currentChar)) { result = result.concat("_"); } - result = result.concat(Character.toString(currentChar).toLowerCase()); + result = result.concat(Character.toString(currentChar).toLowerCase(Locale.ROOT)); } return result; } diff --git a/Foundation/org.egovframe.rte.fdl.string/src/test/java/org/egovframe/rte/fdl/string/EgovStringUtilLocaleTest.java b/Foundation/org.egovframe.rte.fdl.string/src/test/java/org/egovframe/rte/fdl/string/EgovStringUtilLocaleTest.java new file mode 100644 index 00000000..b47ab019 --- /dev/null +++ b/Foundation/org.egovframe.rte.fdl.string/src/test/java/org/egovframe/rte/fdl/string/EgovStringUtilLocaleTest.java @@ -0,0 +1,44 @@ +package org.egovframe.rte.fdl.string; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * 식별자 변환 메서드들이 JVM 기본 로케일과 무관하게 동작하는지 검증한다. + * + *

터키어·아제르바이잔어 로케일에서는 인자 없는 toUpperCase()/toLowerCase()가 + * i ↔ İ(U+0130) / I ↔ ı(U+0131)로 변환한다. 같은 저장소의 CamelCaseUtil은 + * Character 단위 변환을 써서 이 문제가 없다.

+ */ +public class EgovStringUtilLocaleTest { + + private void underTurkishLocale(Runnable body) { + Locale original = Locale.getDefault(); + try { + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + body.run(); + } finally { + Locale.setDefault(original); + } + } + + @Test + @DisplayName("convertToCamelCase는 로케일과 무관하게 ASCII 규칙으로 변환한다") + public void convertToCamelCaseIsLocaleIndependent() { + underTurkishLocale(() -> + assertEquals("printStatus", EgovStringUtil.convertToCamelCase("PRINT_STATUS", '_'))); + assertEquals("printStatus", EgovStringUtil.convertToCamelCase("PRINT_STATUS", '_')); + } + + @Test + @DisplayName("convertToUnderScore는 로케일과 무관하게 ASCII 규칙으로 변환한다") + public void convertToUnderScoreIsLocaleIndependent() { + underTurkishLocale(() -> + assertEquals("print_id", EgovStringUtil.convertToUnderScore("printId"))); + assertEquals("print_id", EgovStringUtil.convertToUnderScore("printId")); + } +}