From e3ffdd3b7cb3e1def7ddedbebc406c72b7e2927d Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 28 May 2025 00:32:35 +0200 Subject: [PATCH] UIDefaultsLoader: improved error reporting and added more unit tests --- .../com/formdev/flatlaf/UIDefaultsLoader.java | 50 +++++++------- .../formdev/flatlaf/TestUIDefaultsLoader.java | 69 +++++++++++++++++++ 2 files changed, 96 insertions(+), 23 deletions(-) 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 81a58196..a2e614ec 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -649,22 +649,26 @@ class UIDefaultsLoader if( value.indexOf( ',' ) >= 0 ) { // Syntax: top,left,bottom,right[,lineColor[,lineThickness[,arc]]] List parts = splitFunctionParams( value, ',' ); - Insets insets = parseInsets( value ); - ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty()) - ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) - : null; - float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) - ? parseFloat( parts.get( 5 ) ) - : 1f; - int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty() - ? parseInteger( parts.get( 6 ) ) - : -1; + try { + Insets insets = parseInsets( value ); + ColorUIResource lineColor = (parts.size() >= 5 && !parts.get( 4 ).isEmpty()) + ? (ColorUIResource) parseColorOrFunction( resolver.apply( parts.get( 4 ) ), resolver ) + : null; + float lineThickness = (parts.size() >= 6 && !parts.get( 5 ).isEmpty()) + ? parseFloat( parts.get( 5 ) ) + : 1f; + int arc = (parts.size() >= 7) && !parts.get( 6 ).isEmpty() + ? parseInteger( parts.get( 6 ) ) + : -1; - return (LazyValue) t -> { - return (lineColor != null || arc > 0) - ? new FlatLineBorder( insets, lineColor, lineThickness, arc ) - : new FlatEmptyBorder( insets ); - }; + return (LazyValue) t -> { + return (lineColor != null || arc > 0) + ? new FlatLineBorder( insets, lineColor, lineThickness, arc ) + : new FlatEmptyBorder( insets ); + }; + } catch( RuntimeException ex ) { + throw new IllegalArgumentException( "invalid border '" + value + "' (" + ex.getMessage() + ")" ); + } } else return parseInstance( value, resolver, addonClassLoaders ); } @@ -735,7 +739,7 @@ class UIDefaultsLoader Integer.parseInt( numbers.get( 1 ) ), Integer.parseInt( numbers.get( 2 ) ), Integer.parseInt( numbers.get( 3 ) ) ); - } catch( NumberFormatException ex ) { + } catch( NumberFormatException | IndexOutOfBoundsException ex ) { throw new IllegalArgumentException( "invalid insets '" + value + "'" ); } } @@ -748,7 +752,7 @@ class UIDefaultsLoader return new DimensionUIResource( Integer.parseInt( numbers.get( 0 ) ), Integer.parseInt( numbers.get( 1 ) ) ); - } catch( NumberFormatException ex ) { + } catch( NumberFormatException | IndexOutOfBoundsException ex ) { throw new IllegalArgumentException( "invalid size '" + value + "'" ); } } @@ -1379,17 +1383,17 @@ class UIDefaultsLoader break; } } - } catch( IOException ex ) { - throw new IllegalArgumentException( ex ); + } catch( RuntimeException | IOException ex ) { + throw new IllegalArgumentException( "invalid font '" + value + "' (" + ex.getMessage() + ")" ); } if( style != -1 && styleChange != 0 ) - throw new IllegalArgumentException( "can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic') in '" + value + "'" ); + throw new IllegalArgumentException( "invalid font '" + value + "': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" ); if( styleChange != 0 ) { if( (styleChange & Font.BOLD) != 0 && (styleChange & (Font.BOLD << 16)) != 0 ) - throw new IllegalArgumentException( "can not use '+bold' and '-bold' in '" + value + "'" ); + throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+bold' and '-bold'" ); if( (styleChange & Font.ITALIC) != 0 && (styleChange & (Font.ITALIC << 16)) != 0 ) - throw new IllegalArgumentException( "can not use '+italic' and '-italic' in '" + value + "'" ); + throw new IllegalArgumentException( "invalid font '" + value + "': can not use '+italic' and '-italic'" ); } font = new FlatLaf.ActiveFont( baseFontKey, families, style, styleChange, absoluteSize, relativeSize, scaleSize ); @@ -1529,7 +1533,7 @@ class UIDefaultsLoader return (LazyValue) t -> { return new GrayFilter( brightness, contrast, alpha ); }; - } catch( NumberFormatException ex ) { + } catch( NumberFormatException | IndexOutOfBoundsException ex ) { throw new IllegalArgumentException( "invalid gray filter '" + value + "'" ); } } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java index 28c36c89..da9f52e9 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java @@ -18,6 +18,7 @@ package com.formdev.flatlaf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; import java.awt.Color; import java.awt.Dimension; @@ -29,6 +30,7 @@ import javax.swing.border.Border; import javax.swing.UIDefaults.ActiveValue; import javax.swing.UIDefaults.LazyValue; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import com.formdev.flatlaf.ui.FlatEmptyBorder; import com.formdev.flatlaf.ui.FlatLineBorder; import com.formdev.flatlaf.util.DerivedColor; @@ -452,6 +454,73 @@ public class TestUIDefaultsLoader return ((LazyValue)v).createValue( null ); } + //---- invalid values ----------------------------------------------------- + + @Test + void parseInvalidValue() { + assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", null ) ); + assertThrows( new NumberFormatException( "invalid integer or float '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", null ) ); + assertThrows( new NumberFormatException( "invalid integer or float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", null ) ); + + assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3,4'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3,4", null ) ); + assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", null ) ); + assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", null ) ); + assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", null ) ); + assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", null ) ); + assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", null ) ); + } + + @Test + void parseInvalidValueWithJavaType() { + assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", boolean.class ) ); + assertThrows( new IllegalArgumentException( "invalid boolean 'falseyy'" ), () -> UIDefaultsLoader.parseValue( "dummy", "falseyy", Boolean.class ) ); + + assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", char.class ) ); + assertThrows( new IllegalArgumentException( "invalid character 'abc'" ), () -> UIDefaultsLoader.parseValue( "dummyChar", "abc", Character.class ) ); + assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", int.class ) ); + assertThrows( new NumberFormatException( "invalid integer '123abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "123abc", Integer.class ) ); + assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", float.class ) ); + assertThrows( new NumberFormatException( "invalid float '1.23abc'" ), () -> UIDefaultsLoader.parseValue( "dummyWidth", "1.23abc", Float.class ) ); + + assertThrows( new IllegalArgumentException( "invalid insets '1,abc,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,abc,3", Insets.class ) ); + assertThrows( new IllegalArgumentException( "invalid insets '1,2,3'" ), () -> UIDefaultsLoader.parseValue( "dummyInsets", "1,2,3", Insets.class ) ); + assertThrows( new IllegalArgumentException( "invalid size '1abc'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1abc", Dimension.class ) ); + assertThrows( new IllegalArgumentException( "invalid size '1'" ), () -> UIDefaultsLoader.parseValue( "dummySize", "1", Dimension.class ) ); + assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummy", "#f0", Color.class ) ); + assertThrows( new IllegalArgumentException( "invalid color '#f0'" ), () -> UIDefaultsLoader.parseValue( "dummyColor", "#f0", Color.class ) ); + } + + @Test + void parseInvalidBorders() { + assertThrows( new IllegalArgumentException( "invalid border '1,abc,3,4' (invalid insets '1,abc,3,4')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,abc,3,4", null ) ); + assertThrows( new IllegalArgumentException( "invalid border '1,2,3' (invalid insets '1,2,3')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3", null ) ); + assertThrows( new IllegalArgumentException( "invalid border '1,2,3,,,' (invalid insets '1,2,3,,,')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,,,", null ) ); + assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f0' (invalid color '#f0')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f0", null ) ); + assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5abc' (invalid float '2.5abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5abc", null ) ); + assertThrows( new IllegalArgumentException( "invalid border '1,2,3,4,#f00,2.5,6abc' (invalid integer '6abc')" ), () -> UIDefaultsLoader.parseValue( "dummyBorder", "1,2,3,4,#f00,2.5,6abc", null ) ); + } + + @Test + void parseInvalidFonts() { + // size + assertThrows( new IllegalArgumentException( "invalid font '12abc' (invalid integer '12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "12abc", null ) ); + assertThrows( new IllegalArgumentException( "invalid font '+12abc' (invalid integer '+12abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+12abc", null ) ); + assertThrows( new IllegalArgumentException( "invalid font '+3abc' (invalid integer '+3abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+3abc", null ) ); + assertThrows( new IllegalArgumentException( "invalid font '-4abc' (invalid integer '-4abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "-4abc", null ) ); + assertThrows( new IllegalArgumentException( "invalid font '150abc%' (invalid integer '150abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "150abc%", null ) ); + assertThrows( new IllegalArgumentException( "invalid font 'bold 13abc Monospaced' (invalid integer '13abc')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold 13abc Monospaced", null ) ); + + // invalid combinations of styles + assertThrows( new IllegalArgumentException( "invalid font 'bold +italic': can not mix absolute style (e.g. 'bold') with derived style (e.g. '+italic')" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "bold +italic", null ) ); + assertThrows( new IllegalArgumentException( "invalid font '+bold -bold': can not use '+bold' and '-bold'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+bold -bold", null ) ); + assertThrows( new IllegalArgumentException( "invalid font '+italic -italic': can not use '+italic' and '-italic'" ), () -> UIDefaultsLoader.parseValue( "dummyFont", "+italic -italic", null ) ); + } + + private void assertThrows( Throwable expected, Executable executable ) { + Throwable actual = assertThrowsExactly( expected.getClass(), executable ); + assertEquals( expected.getMessage(), actual.getMessage() ); + } + //---- class TestInstance ------------------------------------------------- @SuppressWarnings( "EqualsHashCode" ) // Error Prone