This is an automated email from the ASF dual-hosted git repository.

doebele pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/empire-db.git


The following commit(s) were added to refs/heads/master by this push:
     new c50e671c EMPIREDB-455: TextInputControl: Converter for numeric 
ValueExpressions added
c50e671c is described below

commit c50e671cb0c12c3ab08b6361db121a8e78538496
Author: Rainer Döbele <[email protected]>
AuthorDate: Wed Mar 5 13:17:47 2025 +0100

    EMPIREDB-455:
    TextInputControl: Converter for numeric ValueExpressions added
---
 .../empire/jakarta/controls/InputControl.java      |  16 +-
 .../empire/jakarta/controls/TextInputControl.java  | 196 +++++++++++++++++----
 .../apache/empire/jsf2/controls/InputControl.java  |  16 +-
 .../empire/jsf2/controls/TextInputControl.java     | 178 ++++++++++++++++---
 4 files changed, 337 insertions(+), 69 deletions(-)

diff --git 
a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/InputControl.java
 
b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/InputControl.java
index b2f9681f..0a612a21 100644
--- 
a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/InputControl.java
+++ 
b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/InputControl.java
@@ -516,11 +516,10 @@ public abstract class InputControl
         boolean evalExpression = !isInputValueExpressionEnabled();
         Object value = ii.getValue(evalExpression);
         if (value instanceof ValueExpression)
-        {
-            input.setValue(null);
-            input.setLocalValueSet(false);
-            input.setValueExpression("value", (ValueExpression) value);
-
+        {   // set value expression
+            ValueExpression current = input.getValueExpression("value");
+            if (current!=value)
+                setInputValueExpression(input, (ValueExpression)value, ii);
             // Object check = 
((ValueExpression)value).getValue(FacesContext.getCurrentInstance().getELContext());
             // log.info("Expression value is {}.", check);
         }
@@ -532,6 +531,13 @@ public abstract class InputControl
             addRemoveValueNullStyle(input, ObjectUtils.isEmpty(value));
         }
     }
+    
+    protected void setInputValueExpression(UIInput input, ValueExpression 
value, InputInfo ii)
+    {
+        input.setValue(null);
+        input.setLocalValueSet(false);
+        input.setValueExpression("value", value);
+    }
 
     protected void clearSubmittedValue(UIInput input)
     {
diff --git 
a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/TextInputControl.java
 
b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/TextInputControl.java
index 9a621310..dd1e40bc 100644
--- 
a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/TextInputControl.java
+++ 
b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/controls/TextInputControl.java
@@ -19,8 +19,11 @@
 package org.apache.empire.jakarta.controls;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.sql.Timestamp;
 import java.text.DateFormat;
+import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -28,26 +31,34 @@ import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 
-import jakarta.faces.component.UIComponent;
-import jakarta.faces.component.UIInput;
-import jakarta.faces.component.html.HtmlInputText;
-import jakarta.faces.component.html.HtmlOutputText;
-import jakarta.faces.component.html.HtmlPanelGroup;
-import jakarta.faces.context.FacesContext;
-import jakarta.faces.context.ResponseWriter;
-import jakarta.faces.event.PhaseId;
-
 import org.apache.empire.commons.ObjectUtils;
 import org.apache.empire.commons.Options;
 import org.apache.empire.commons.StringUtils;
 import org.apache.empire.data.Column;
 import org.apache.empire.data.DataType;
+import org.apache.empire.db.exceptions.FieldIllegalValueException;
 import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.exceptions.InvalidValueException;
 import org.apache.empire.exceptions.UnexpectedReturnValueException;
+import org.apache.empire.jakarta.components.ControlTag;
+import org.apache.empire.jakarta.components.InputTag;
 import org.apache.empire.jakarta.utils.TagStyleClass;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import jakarta.el.ValueExpression;
+import jakarta.faces.component.StateHolder;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIInput;
+import jakarta.faces.component.html.HtmlForm;
+import jakarta.faces.component.html.HtmlInputText;
+import jakarta.faces.component.html.HtmlOutputText;
+import jakarta.faces.component.html.HtmlPanelGroup;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.context.ResponseWriter;
+import jakarta.faces.convert.Converter;
+import jakarta.faces.event.PhaseId;
+
 public class TextInputControl extends InputControl
 {
     private static final Logger log                   = 
LoggerFactory.getLogger(TextInputControl.class);
@@ -62,6 +73,97 @@ public class TextInputControl extends InputControl
 
     public static final String  FRACTION_DIGITS       = "fraction-digits:";
     
+    /**
+     * NumberInputConverter
+     * Formats a decimal value based on the NumberFormat
+     */
+    public static class NumberInputConverter implements Converter<Object>, 
StateHolder
+    {
+        private NumberFormat nf;
+        private boolean trans = false;
+        
+        /*
+         * Must have a default constructor!
+         */
+        public NumberInputConverter()
+        {
+            this.nf = null;
+        }
+        
+        public NumberInputConverter(NumberFormat nf)
+        {
+            this.nf = nf;
+        }
+
+        @Override
+        public Object saveState(FacesContext context)
+        {
+            return nf;
+        }
+
+        @Override
+        public void restoreState(FacesContext context, Object state)
+        {
+            this.nf = (NumberFormat)state;
+        }
+
+        @Override
+        public boolean isTransient()
+        {
+            return trans;
+        }
+
+        @Override
+        public void setTransient(boolean newTransientValue)
+        {
+            this.trans = newTransientValue;
+        }
+        
+        @Override
+        public String getAsString(FacesContext context, UIComponent component, 
Object value)
+        {
+            return (nf!=null ?  nf.format(value) : StringUtils.valueOf(value));
+        }
+
+        @Override
+        public Object getAsObject(FacesContext context, UIComponent component, 
String value)
+        {
+            if (ObjectUtils.isEmpty(value))
+                return null;
+            try
+            {   // parse
+                if (nf==null)
+                    return new BigDecimal(value);
+                // parse now
+                Number number = nf.parse(value);
+                if (number instanceof BigDecimal)
+                {   // Round to scale
+                    int scale = nf.getMaximumFractionDigits();
+                    number = ((BigDecimal)number).setScale(scale, 
RoundingMode.HALF_UP);
+                }
+                return number;
+            }
+            catch (ParseException e)
+            {   // find column
+                UIComponent inputComp = component.getParent();
+                while (inputComp!=null) {
+                    // set the tag
+                    if ((inputComp instanceof InputTag) || (inputComp 
instanceof ControlTag))
+                    {   // Found an InputTag or ControlTag
+                        Object column = 
inputComp.getAttributes().get("column");
+                        if (column instanceof Column)
+                            throw new 
FieldIllegalValueException((Column)column, value);
+                    }
+                    inputComp = inputComp.getParent();
+                    if (inputComp instanceof HtmlForm)
+                        break;
+                }
+                // Just throw an InvalidValueException
+                throw new InvalidValueException(value);
+            }
+        }
+    }
+    
     private Class<? extends HtmlInputText> inputComponentClass;
 
     public TextInputControl(String name, Class<? extends HtmlInputText> 
inputComponentClass)
@@ -184,6 +286,29 @@ public class TextInputControl extends InputControl
             super.setInputStyleClass(input, cssStyleClass);
     }
 
+    @Override
+    protected void setInputValueExpression(UIInput input, ValueExpression 
value, InputInfo ii)
+    {
+        super.setInputValueExpression(input, value, ii);
+        // establish converter for decimal
+        DataType dataType = ii.getColumn().getDataType();
+        if (dataType.isNumeric())
+        {   // get number format
+            NumberFormat nf;
+            if (dataType == DataType.INTEGER || dataType == DataType.AUTOINC)
+                nf = NumberFormat.getIntegerInstance(ii.getLocale()); // 
Integer only
+            else {
+                // Decimal or Float
+                nf = getNumberFormat(dataType, ii, ii.getColumn());
+                // ParseBigDecimal
+                if (nf instanceof DecimalFormat)
+                    
((DecimalFormat)nf).setParseBigDecimal((dataType==DataType.DECIMAL));
+            }
+            // create converter
+            input.setConverter(new NumberInputConverter(nf));
+        }
+    }
+    
     // ------- parsing -------
 
     @Override
@@ -203,12 +328,17 @@ public class TextInputControl extends InputControl
         }
         // Check other types
         if (type == DataType.INTEGER)
-        {
-               return parseInteger(value, ii.getLocale());
+        {   // get IntegerFormat and parse
+            NumberFormat nf = NumberFormat.getIntegerInstance(ii.getLocale());
+               return parseNumber(value, nf);
         }
         if (type == DataType.DECIMAL || type == DataType.FLOAT)
-        {
-               return parseDecimal(value, ii.getLocale());
+        {   // get number format
+            NumberFormat nf = getNumberFormat(type, ii, column);
+            if (nf instanceof DecimalFormat)
+                
((DecimalFormat)nf).setParseBigDecimal((type==DataType.DECIMAL));
+            // parse
+            return parseNumber(value, nf);
         }
         if (type == DataType.DATE || type == DataType.DATETIME || type == 
DataType.TIMESTAMP)
         {
@@ -509,7 +639,7 @@ public class TextInputControl extends InputControl
         String type = 
StringUtils.valueOf(column.getAttribute(Column.COLATTR_NUMBER_TYPE));
         boolean isInteger = "Integer".equalsIgnoreCase(type);
         NumberFormat nf = (isInteger) ? NumberFormat.getIntegerInstance(locale)
-                                      : 
NumberFormat.getNumberInstance(locale); 
+                                      : NumberFormat.getNumberInstance(locale);
         // Groups Separator?
         Object groupSep = column.getAttribute(Column.COLATTR_NUMBER_GROUPSEP);
         nf.setGroupingUsed(groupSep != null && 
ObjectUtils.getBoolean(groupSep));
@@ -550,6 +680,12 @@ public class TextInputControl extends InputControl
             nf.setMinimumFractionDigits(minFactionDigits);
             nf.setMaximumFractionDigits(maxFactionDigits);
         }
+        else if (!isInteger && dataType==DataType.DECIMAL) 
+        {   // Detect from column
+            int length = (int)column.getSize();
+            int maxFactionDigits = (int)(column.getSize()*10)-(length*10);
+            nf.setMaximumFractionDigits(maxFactionDigits);
+        }
         // IntegerDigits (left-padding)
         Object intDigits = column.getAttribute(Column.COLATTR_INTEGER_DIGITS);
         if (intDigits != null) {
@@ -633,36 +769,28 @@ public class TextInputControl extends InputControl
 
     // ------- value parsing -------
 
-    protected Object parseInteger(String value, Locale locale)
-    {
-        NumberFormat nf = NumberFormat.getIntegerInstance();
-        // Parse String
-        try
-        {
-            return nf.parseObject(value);
-        } catch (ParseException pe) {
-            throw new NumberFormatException("Not a number: " + value + " 
Exception: " + pe.toString());
-        }        
-    }
-
-    protected Object parseDecimal(String value, Locale locale)
+    protected Object parseNumber(String value, NumberFormat nf)
     {
-        NumberFormat nf = NumberFormat.getNumberInstance(locale);
-        // Parse String
         try
+        {   // Parse Number
+            Number number = nf.parse(value);
+            if (number instanceof BigDecimal)
+            {   // Round to scale
+                int scale = nf.getMaximumFractionDigits();
+                number = ((BigDecimal)number).setScale(scale, 
RoundingMode.HALF_UP);
+            }
+            return number;
+        }
+        catch (ParseException pe)
         {
-            return nf.parseObject(value);
-        } catch (ParseException pe) {
             throw new NumberFormatException("Not a number: " + value + " 
Exception: " + pe.toString());
         }
     }
 
     protected Object parseDate(String s, DateFormat df)
     {
-        // Try to convert
         try
-        {
-            // Parse Date
+        {   // Parse Date
             df.setLenient(true);
             return df.parseObject(s);
         } catch (ParseException pe) {
diff --git 
a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/InputControl.java
 
b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/InputControl.java
index 95f7ec2a..51c23446 100644
--- 
a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/InputControl.java
+++ 
b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/InputControl.java
@@ -516,11 +516,10 @@ public abstract class InputControl
         boolean evalExpression = !isInputValueExpressionEnabled();
         Object value = ii.getValue(evalExpression);
         if (value instanceof ValueExpression)
-        {
-            input.setValue(null);
-            input.setLocalValueSet(false);
-            input.setValueExpression("value", (ValueExpression) value);
-
+        {   // set value expression
+            ValueExpression current = input.getValueExpression("value");
+            if (current!=value)
+                setInputValueExpression(input, (ValueExpression)value, ii);
             // Object check = 
((ValueExpression)value).getValue(FacesContext.getCurrentInstance().getELContext());
             // log.info("Expression value is {}.", check);
         }
@@ -532,6 +531,13 @@ public abstract class InputControl
             addRemoveValueNullStyle(input, ObjectUtils.isEmpty(value));
         }
     }
+    
+    protected void setInputValueExpression(UIInput input, ValueExpression 
value, InputInfo ii)
+    {
+        input.setValue(null);
+        input.setLocalValueSet(false);
+        input.setValueExpression("value", value);
+    }
 
     protected void clearSubmittedValue(UIInput input)
     {
diff --git 
a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/TextInputControl.java
 
b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/TextInputControl.java
index acac4d34..066f9020 100644
--- 
a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/TextInputControl.java
+++ 
b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/TextInputControl.java
@@ -19,8 +19,11 @@
 package org.apache.empire.jsf2.controls;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.sql.Timestamp;
 import java.text.DateFormat;
+import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -28,13 +31,17 @@ import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 
+import javax.el.ValueExpression;
+import javax.faces.component.StateHolder;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIInput;
+import javax.faces.component.html.HtmlForm;
 import javax.faces.component.html.HtmlInputText;
 import javax.faces.component.html.HtmlOutputText;
 import javax.faces.component.html.HtmlPanelGroup;
 import javax.faces.context.FacesContext;
 import javax.faces.context.ResponseWriter;
+import javax.faces.convert.Converter;
 import javax.faces.event.PhaseId;
 
 import org.apache.empire.commons.ObjectUtils;
@@ -42,8 +49,12 @@ import org.apache.empire.commons.Options;
 import org.apache.empire.commons.StringUtils;
 import org.apache.empire.data.Column;
 import org.apache.empire.data.DataType;
+import org.apache.empire.db.exceptions.FieldIllegalValueException;
 import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.exceptions.InvalidValueException;
 import org.apache.empire.exceptions.UnexpectedReturnValueException;
+import org.apache.empire.jsf2.components.ControlTag;
+import org.apache.empire.jsf2.components.InputTag;
 import org.apache.empire.jsf2.utils.TagStyleClass;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -62,6 +73,97 @@ public class TextInputControl extends InputControl
 
     public static final String  FRACTION_DIGITS       = "fraction-digits:";
     
+    /**
+     * NumberInputConverter
+     * Formats a decimal value based on the NumberFormat
+     */
+    public static class NumberInputConverter implements Converter, StateHolder
+    {
+        private NumberFormat nf;
+        private boolean trans = false;
+        
+        /*
+         * Must have a default constructor!
+         */
+        public NumberInputConverter()
+        {
+            this.nf = null;
+        }
+        
+        public NumberInputConverter(NumberFormat nf)
+        {
+            this.nf = nf;
+        }
+
+        @Override
+        public Object saveState(FacesContext context)
+        {
+            return nf;
+        }
+
+        @Override
+        public void restoreState(FacesContext context, Object state)
+        {
+            this.nf = (NumberFormat)state;
+        }
+
+        @Override
+        public boolean isTransient()
+        {
+            return trans;
+        }
+
+        @Override
+        public void setTransient(boolean newTransientValue)
+        {
+            this.trans = newTransientValue;
+        }
+        
+        @Override
+        public String getAsString(FacesContext context, UIComponent component, 
Object value)
+        {
+            return (nf!=null ?  nf.format(value) : StringUtils.valueOf(value));
+        }
+
+        @Override
+        public Object getAsObject(FacesContext context, UIComponent component, 
String value)
+        {
+            if (ObjectUtils.isEmpty(value))
+                return null;
+            try
+            {   // parse
+                if (nf==null)
+                    return new BigDecimal(value);
+                // parse now
+                Number number = nf.parse(value);
+                if (number instanceof BigDecimal)
+                {   // Round to scale
+                    int scale = nf.getMaximumFractionDigits();
+                    number = ((BigDecimal)number).setScale(scale, 
RoundingMode.HALF_UP);
+                }
+                return number;
+            }
+            catch (ParseException e)
+            {   // find column
+                UIComponent inputComp = component.getParent();
+                while (inputComp!=null) {
+                    // set the tag
+                    if ((inputComp instanceof InputTag) || (inputComp 
instanceof ControlTag))
+                    {   // Found an InputTag or ControlTag
+                        Object column = 
inputComp.getAttributes().get("column");
+                        if (column instanceof Column)
+                            throw new 
FieldIllegalValueException((Column)column, value);
+                    }
+                    inputComp = inputComp.getParent();
+                    if (inputComp instanceof HtmlForm)
+                        break;
+                }
+                // Just throw an InvalidValueException
+                throw new InvalidValueException(value);
+            }
+        }
+    }
+    
     private Class<? extends HtmlInputText> inputComponentClass;
 
     public TextInputControl(String name, Class<? extends HtmlInputText> 
inputComponentClass)
@@ -184,6 +286,29 @@ public class TextInputControl extends InputControl
             super.setInputStyleClass(input, cssStyleClass);
     }
 
+    @Override
+    protected void setInputValueExpression(UIInput input, ValueExpression 
value, InputInfo ii)
+    {
+        super.setInputValueExpression(input, value, ii);
+        // establish converter for decimal
+        DataType dataType = ii.getColumn().getDataType();
+        if (dataType.isNumeric())
+        {   // get number format
+            NumberFormat nf;
+            if (dataType == DataType.INTEGER || dataType == DataType.AUTOINC)
+                nf = NumberFormat.getIntegerInstance(ii.getLocale()); // 
Integer only
+            else {
+                // Decimal or Float
+                nf = getNumberFormat(dataType, ii, ii.getColumn());
+                // ParseBigDecimal
+                if (nf instanceof DecimalFormat)
+                    
((DecimalFormat)nf).setParseBigDecimal((dataType==DataType.DECIMAL));
+            }
+            // create converter
+            input.setConverter(new NumberInputConverter(nf));
+        }
+    }
+    
     // ------- parsing -------
 
     @Override
@@ -203,12 +328,17 @@ public class TextInputControl extends InputControl
         }
         // Check other types
         if (type == DataType.INTEGER)
-        {
-               return parseInteger(value, ii.getLocale());
+        {   // get IntegerFormat and parse
+            NumberFormat nf = NumberFormat.getIntegerInstance(ii.getLocale());
+               return parseNumber(value, nf);
         }
         if (type == DataType.DECIMAL || type == DataType.FLOAT)
-        {
-               return parseDecimal(value, ii.getLocale());
+        {   // get number format
+            NumberFormat nf = getNumberFormat(type, ii, column);
+            if (nf instanceof DecimalFormat)
+                
((DecimalFormat)nf).setParseBigDecimal((type==DataType.DECIMAL));
+            // parse
+            return parseNumber(value, nf);
         }
         if (type == DataType.DATE || type == DataType.DATETIME || type == 
DataType.TIMESTAMP)
         {
@@ -509,7 +639,7 @@ public class TextInputControl extends InputControl
         String type = 
StringUtils.valueOf(column.getAttribute(Column.COLATTR_NUMBER_TYPE));
         boolean isInteger = "Integer".equalsIgnoreCase(type);
         NumberFormat nf = (isInteger) ? NumberFormat.getIntegerInstance(locale)
-                                      : 
NumberFormat.getNumberInstance(locale); 
+                                      : NumberFormat.getNumberInstance(locale);
         // Groups Separator?
         Object groupSep = column.getAttribute(Column.COLATTR_NUMBER_GROUPSEP);
         nf.setGroupingUsed(groupSep != null && 
ObjectUtils.getBoolean(groupSep));
@@ -550,6 +680,12 @@ public class TextInputControl extends InputControl
             nf.setMinimumFractionDigits(minFactionDigits);
             nf.setMaximumFractionDigits(maxFactionDigits);
         }
+        else if (!isInteger && dataType==DataType.DECIMAL) 
+        {   // Detect from column
+            int length = (int)column.getSize();
+            int maxFactionDigits = (int)(column.getSize()*10)-(length*10);
+            nf.setMaximumFractionDigits(maxFactionDigits);
+        }
         // IntegerDigits (left-padding)
         Object intDigits = column.getAttribute(Column.COLATTR_INTEGER_DIGITS);
         if (intDigits != null) {
@@ -633,36 +769,28 @@ public class TextInputControl extends InputControl
 
     // ------- value parsing -------
 
-    protected Object parseInteger(String value, Locale locale)
+    protected Object parseNumber(String value, NumberFormat nf)
     {
-        NumberFormat nf = NumberFormat.getIntegerInstance();
-        // Parse String
-        try
-        {
-            return nf.parseObject(value);
-        } catch (ParseException pe) {
-            throw new NumberFormatException("Not a number: " + value + " 
Exception: " + pe.toString());
-        }        
-    }
-
-    protected Object parseDecimal(String value, Locale locale)
-    {
-        NumberFormat nf = NumberFormat.getNumberInstance(locale);
-        // Parse String
         try
+        {   // Parse Number
+            Number number = nf.parse(value);
+            if (number instanceof BigDecimal)
+            {   // Round to scale
+                int scale = nf.getMaximumFractionDigits();
+                number = ((BigDecimal)number).setScale(scale, 
RoundingMode.HALF_UP);
+            }
+            return number;
+        }
+        catch (ParseException pe)
         {
-            return nf.parseObject(value);
-        } catch (ParseException pe) {
             throw new NumberFormatException("Not a number: " + value + " 
Exception: " + pe.toString());
         }
     }
 
     protected Object parseDate(String s, DateFormat df)
     {
-        // Try to convert
         try
-        {
-            // Parse Date
+        {   // Parse Date
             df.setLenient(true);
             return df.parseObject(s);
         } catch (ParseException pe) {

Reply via email to