diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java index 42333ad9..aeeb2a29 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -392,7 +392,7 @@ class UIDefaultsLoader // Syntax: lazy(uiKey) if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { resultValueType[0] = ValueType.LAZY; - String uiKey = value.substring( 5, value.length() - 1 ).trim(); + String uiKey = StringUtils.substringTrimmed( value, 5, value.length() - 1 ); return (LazyValue) t -> { return lazyUIManagerGet( uiKey ); }; @@ -514,7 +514,7 @@ class UIDefaultsLoader private static Object parseBorder( String value, Function resolver, List addonClassLoaders ) { if( value.indexOf( ',' ) >= 0 ) { // top,left,bottom,right[,lineColor[,lineThickness]] - List parts = split( value, ',' ); + List parts = StringUtils.split( value, ',', true, false ); Insets insets = parseInsets( value ); ColorUIResource lineColor = (parts.size() >= 5) ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver, true ) @@ -571,7 +571,7 @@ class UIDefaultsLoader } private static Insets parseInsets( String value ) { - List numbers = split( value, ',' ); + List numbers = StringUtils.split( value, ',', true, false ); try { return new InsetsUIResource( Integer.parseInt( numbers.get( 0 ) ), @@ -584,7 +584,7 @@ class UIDefaultsLoader } private static Dimension parseDimension( String value ) { - List numbers = split( value, ',' ); + List numbers = StringUtils.split( value, ',', true, false ); try { return new DimensionUIResource( Integer.parseInt( numbers.get( 0 ) ), @@ -672,7 +672,7 @@ class UIDefaultsLoader return null; } - String function = value.substring( 0, paramsStart ).trim(); + String function = StringUtils.substringTrimmed( value, 0, paramsStart ); List params = splitFunctionParams( value.substring( paramsStart + 1, value.length() - 1 ), ',' ); if( params.isEmpty() ) throwMissingParametersException( value ); @@ -1075,7 +1075,7 @@ class UIDefaultsLoader } private static Object parseGrayFilter( String value ) { - List numbers = split( value, ',' ); + List numbers = StringUtils.split( value, ',', true, false ); try { int brightness = Integer.parseInt( numbers.get( 0 ) ); int contrast = Integer.parseInt( numbers.get( 1 ) ); @@ -1089,20 +1089,6 @@ class UIDefaultsLoader } } - /** - * Split string and trim parts. - */ - private static List split( String str, char delim ) { - List result = StringUtils.split( str, delim ); - - // trim strings - int size = result.size(); - for( int i = 0; i < size; i++ ) - result.set( i, result.get( i ).trim() ); - - return result; - } - /** * Splits function parameters and allows using functions as parameters. * In other words: Delimiters surrounded by '(' and ')' are ignored. @@ -1119,11 +1105,11 @@ class UIDefaultsLoader else if( ch == ')' ) nestLevel--; else if( nestLevel == 0 && ch == delim ) { - strs.add( str.substring( start, i ).trim() ); + strs.add( StringUtils.substringTrimmed( str, start, i ) ); start = i + 1; } } - strs.add( str.substring( start ).trim() ); + strs.add( StringUtils.substringTrimmed( str, start ) ); return strs; } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java index 6f21ba2b..6cb36be5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStylingSupport.java @@ -252,7 +252,7 @@ public class FlatStylingSupport if( style instanceof String ) { // handle style in CSS syntax String str = (String) style; - if( str.trim().isEmpty() ) + if( StringUtils.isTrimmedEmpty( str ) ) return null; return applyStyle( parse( str ), applyProperty ); @@ -307,26 +307,21 @@ public class FlatStylingSupport public static Map parse( String style ) throws IllegalArgumentException { - if( style == null || style.trim().isEmpty() ) + if( style == null || StringUtils.isTrimmedEmpty( style ) ) return null; Map map = null; // split style into parts and process them - for( String part : StringUtils.split( style, ';' ) ) { - // ignore empty parts - part = part.trim(); - if( part.isEmpty() ) - continue; - + for( String part : StringUtils.split( style, ';', true, true ) ) { // find separator colon int sepIndex = part.indexOf( ':' ); if( sepIndex < 0 ) throw new IllegalArgumentException( "missing colon in '" + part + "'" ); // split into key and value - String key = part.substring( 0, sepIndex ).trim(); - String value = part.substring( sepIndex + 1 ).trim(); + String key = StringUtils.substringTrimmed( part, 0, sepIndex ); + String value = StringUtils.substringTrimmed( part, sepIndex + 1 ); if( key.isEmpty() ) throw new IllegalArgumentException( "missing key in '" + part + "'" ); if( value.isEmpty() ) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/StringUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/StringUtils.java index 2f952014..157e1b50 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/StringUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/StringUtils.java @@ -79,18 +79,66 @@ public class StringUtils return strs; } - private static void add( List strs, String str, int begin, int end, boolean trim, boolean excludeEmpty ) { + private static void add( List strs, String str, int beginIndex, int endIndex, + boolean trim, boolean excludeEmpty ) + { if( trim ) { - // skip leading whitespace - while( begin < end && str.charAt( begin ) <= ' ' ) - begin++; - - // skip trailing whitespace - while( begin < end && str.charAt( end - 1 ) <= ' ' ) - end--; + beginIndex = trimBegin( str, beginIndex, endIndex ); + endIndex = trimEnd( str, beginIndex, endIndex ); } - if( !excludeEmpty || end > begin ) - strs.add( str.substring( begin, end ) ); + if( !excludeEmpty || endIndex > beginIndex ) + strs.add( str.substring( beginIndex, endIndex ) ); + } + + /** + * This is equal to {@code str.substring( beginIndex, endIndex ).trim()}, + * but avoids temporary untrimmed substring allocation. + * If the trimmed string is empty, a shared empty string is returned. + * + * @since 2 + */ + public static String substringTrimmed( String str, int beginIndex ) { + return substringTrimmed( str, beginIndex, str.length() ); + } + + /** + * This is equal to {@code str.substring( beginIndex ).trim()}, + * but avoids temporary untrimmed substring allocation. + * If the trimmed string is empty, a shared empty string is returned. + * + * @since 2 + */ + public static String substringTrimmed( String str, int beginIndex, int endIndex ) { + beginIndex = trimBegin( str, beginIndex, endIndex ); + endIndex = trimEnd( str, beginIndex, endIndex ); + return (endIndex > beginIndex) ? str.substring( beginIndex, endIndex ) : ""; + } + + /** + * This is equal to {@code str.trim().isEmpty()}, + * but avoids temporary trimmed substring allocation. + * + * @since 2 + */ + public static boolean isTrimmedEmpty( String str ) { + int length = str.length(); + int beginIndex = trimBegin( str, 0, length ); + int endIndex = trimEnd( str, beginIndex, length ); + return beginIndex >= endIndex; + } + + private static int trimBegin( String str, int beginIndex, int endIndex ) { + // skip leading whitespace + while( beginIndex < endIndex && str.charAt( beginIndex ) <= ' ' ) + beginIndex++; + return beginIndex; + } + + private static int trimEnd( String str, int beginIndex, int endIndex ) { + // skip trailing whitespace + while( beginIndex < endIndex && str.charAt( endIndex - 1 ) <= ' ' ) + endIndex--; + return endIndex; } } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestStringUtils.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestStringUtils.java index 136ebbcf..189741b0 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestStringUtils.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestStringUtils.java @@ -195,4 +195,46 @@ public class TestStringUtils Arrays.asList( "a", "b", "c" ), StringUtils.split( "a\n\n\nb\n\nc", '\n', true, true ) ); } + + @Test + void substringTrimmed() { + testSubstringTrimmed( "", 0 ); + + testSubstringTrimmed( "a", 0 ); + testSubstringTrimmed( "a", 1 ); + + testSubstringTrimmed( "a ", 0 ); + testSubstringTrimmed( " a", 0 ); + testSubstringTrimmed( " a ", 0 ); + + testSubstringTrimmed( " a ", 1 ); + + testSubstringTrimmed( " a ", 0, 3 ); + testSubstringTrimmed( " a ", 1, 4 ); + } + + private void testSubstringTrimmed( String str, int beginIndex ) { + assertEquals( + str.substring( beginIndex ).trim(), + StringUtils.substringTrimmed( str, beginIndex ) ); + } + + private void testSubstringTrimmed( String str, int beginIndex, int endIndex ) { + assertEquals( + str.substring( beginIndex, endIndex ).trim(), + StringUtils.substringTrimmed( str, beginIndex, endIndex ) ); + } + + @Test + void trimmedEmpty() { + testTrimmedEmpty( "" ); + testTrimmedEmpty( "a" ); + testTrimmedEmpty( " a " ); + testTrimmedEmpty( " " ); + testTrimmedEmpty( " " ); + } + + private void testTrimmedEmpty( String str ) { + assertEquals( str.trim().isEmpty(), StringUtils.isTrimmedEmpty( str ) ); + } }