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 216933a8 EMPIREDB-446 SelectInputControl: add support for 
SelectItemGroups
216933a8 is described below

commit 216933a8eae2e557cd367b3c3b58ee7765fa084c
Author: Rainer Döbele <[email protected]>
AuthorDate: Sun Nov 17 11:13:54 2024 +0100

    EMPIREDB-446
    SelectInputControl: add support for SelectItemGroups
---
 .../empire/jsf2/controls/SelectInputControl.java   | 142 +++++++++++++++------
 .../org/apache/empire/commons/ObjectUtils.java     |  54 +++++---
 .../java/org/apache/empire/commons/Options.java    |  29 +++++
 3 files changed, 170 insertions(+), 55 deletions(-)

diff --git 
a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/SelectInputControl.java
 
b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/SelectInputControl.java
index 92c9964a..ae271bd9 100644
--- 
a/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/SelectInputControl.java
+++ 
b/empire-db-jsf2/src/main/java/org/apache/empire/jsf2/controls/SelectInputControl.java
@@ -19,8 +19,10 @@
 package org.apache.empire.jsf2.controls;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import javax.el.ValueExpression;
 import javax.faces.component.UIComponent;
@@ -32,10 +34,12 @@ import javax.faces.component.html.HtmlSelectOneMenu;
 import javax.faces.context.FacesContext;
 import javax.faces.event.PhaseId;
 import javax.faces.model.SelectItem;
+import javax.faces.model.SelectItemGroup;
 
 import org.apache.empire.commons.ObjectUtils;
 import org.apache.empire.commons.OptionEntry;
 import org.apache.empire.commons.Options;
+import org.apache.empire.commons.Options.OptionGroupResolver;
 import org.apache.empire.data.Column;
 import org.apache.empire.exceptions.InvalidArgumentException;
 import org.apache.empire.exceptions.UnexpectedReturnValueException;
@@ -188,6 +192,30 @@ public class SelectInputControl extends InputControl
         return ObjectUtils.isEmpty(currentValue);
     }
 
+    /**
+     * SelectGroup
+     * helper class for building SelectItemGroups
+     */
+    protected static class SelectGroup
+    {
+        private final SelectItemGroup  selectItemGroup;
+        private final List<SelectItem> groupItemList;
+        public SelectGroup(SelectItemGroup  selectItemGroup)
+        {
+            this.selectItemGroup = selectItemGroup;
+            this.groupItemList = new ArrayList<SelectItem>();
+        }
+        public List<SelectItem> getItemList()
+        {
+            return groupItemList;
+        }
+        public void peg()
+        {
+            SelectItem[] items = ObjectUtils.listToArray(SelectItem[].class, 
groupItemList);
+            selectItemGroup.setSelectItems(items);
+        }
+    }
+    
     public void initOptions(UISelectOne input, TextResolver textResolver, 
InputInfo ii)
     {
         // get the options
@@ -200,25 +228,57 @@ public class SelectInputControl extends InputControl
                 log.warn("No options given for select tag {}", 
input.getClientId());
             options = new Options();
         }
+        // list and type
+        Class<?> exprType = 
(Class<?>)input.getAttributes().get(SelectInputControl.VALUE_EXPRESSION_TYPE);
+        List<SelectItem> selectItemList = getSelectItemList(input);
         // current 
         Object currentValue = ii.getValue(true);
         if (isEmptyEntryRequired(input, options, ii, currentValue))
         {   // Empty entry
-            addSelectItem(input, textResolver, new OptionEntry(null, 
getNullText(ii)));
+            addSelectItem(selectItemList, textResolver, new OptionEntry(null, 
getNullText(ii)), exprType);
         }
         if (options != null && options.size() > 0)
-        {   // Add options
+        {   // Option grouping?
+            OptionGroupResolver optionGroupResolver = 
options.getOptionGroupResolver();
+            Map<Object, SelectGroup> groupMap = (optionGroupResolver!=null ? 
new HashMap<Object, SelectGroup>() : null);
+            // Add options
             for (OptionEntry oe : options)
             {   // Option entries
                 if (oe.isActive() || ObjectUtils.compareEqual(oe.getValue(), 
currentValue))
-                {   // add active or current item   
-                    addSelectItem(input, textResolver, oe);
+                {   // add active or current item
+                    List<SelectItem> list = selectItemList; 
+                    if (optionGroupResolver!=null)
+                    {   // get the option group
+                        Object group = optionGroupResolver.getGroup(oe);
+                        if (group!=null)
+                        {   // We have a group
+                            SelectGroup selectGroup = groupMap.get(group);
+                            if (selectGroup==null)
+                            {   // Create a new group
+                                String groupLabel = (group!=null ? 
textResolver.resolveText(group.toString()) : null); 
+                                SelectItemGroup selectItemGroup = new 
SelectItemGroup(groupLabel);
+                                selectItemList.add(selectItemGroup);
+                                // add group to map
+                                selectGroup = new SelectGroup(selectItemGroup);
+                                groupMap.put(group, selectGroup);
+                            }
+                            list = selectGroup.getItemList();
+                        }
+                    }
+                    addSelectItem(list, textResolver, oe, exprType);
                 }
                 else if (log.isDebugEnabled())
                 {   // not active, ignore this one
                     log.debug("Select item {} is not active.", oe.getValue());
                 }
             }
+            // complete groups
+            if (groupMap!=null)
+            {   // Peg all SelectItemGroups
+                for (SelectGroup group : groupMap.values())
+                    group.peg();
+                groupMap.clear();
+            }
         }
     }
     
@@ -234,27 +294,28 @@ public class SelectInputControl extends InputControl
                 input.getChildren().clear();
             return;
         }
+        
+        // check grouping
+        OptionGroupResolver optionGroupResolver = 
options.getOptionGroupResolver();
+        if (optionGroupResolver!=null)
+        {   // not (yet) supported
+            log.debug("SyncOptions is not supported for grouped SelectItems 
for column {}", ii.getColumn().getName());
+            return;
+        }
+        
+        // list and type
+        Class<?> exprType = 
(Class<?>)input.getAttributes().get(SelectInputControl.VALUE_EXPRESSION_TYPE);
+        List<SelectItem> selectItemList = getSelectItemList(input);
+
+        // prepare
         Object currentValue = ii.getValue(true);
         boolean hasEmpty = isEmptyEntryRequired(input, options, ii, 
currentValue);
         // boolean isInsideUIData = ii.isInsideUIData();
         // Compare child-items with options
         Iterator<OptionEntry> ioe = options.iterator();
         OptionEntry oe = (ioe.hasNext() ? ioe.next() : null);
-
-        // get UISelectItems
-        List<UIComponent> childList = input.getChildren();
-        if (childList.isEmpty())
-            childList.add(new UISelectItems());
-        else if (childList.size()>1 && !(childList.get(1) instanceof 
UIParameter))
-            log.warn("Unexpected number of child items ({}) for 
SelectInputControl of column {}", childList.size(), ii.getColumn().getName());
-        UISelectItems items = (UISelectItems) childList.get(0);
-        // get SelectItem list
-        @SuppressWarnings("unchecked")
-        List<SelectItem> selectItemList = (List<SelectItem>) items.getValue();
-        if (selectItemList==null)
-        {   selectItemList = new ArrayList<SelectItem>();
-            items.setValue(selectItemList);
-        }
+        
+        // sync
         Iterator<SelectItem> ico = selectItemList.iterator();
         int lastIndex = 0;
         boolean emptyPresent = false;
@@ -294,13 +355,13 @@ public class SelectInputControl extends InputControl
             input.getChildren().clear();
             if (hasEmpty)
             {   // add empty entry
-                addSelectItem(input, textResolver, new OptionEntry("", 
getNullText(ii)));
+                addSelectItem(selectItemList, textResolver, new 
OptionEntry("", getNullText(ii)), exprType);
             }
             for (OptionEntry opt : options)
             { // Option entries
                 if (opt.isActive() || ObjectUtils.compareEqual(opt.getValue(), 
currentValue))
                 { // add active or current item
-                    addSelectItem(input, textResolver, opt);
+                    addSelectItem(selectItemList, textResolver, opt, exprType);
                 }
             }
             // done
@@ -309,41 +370,42 @@ public class SelectInputControl extends InputControl
         // check empty entry
         if (hasEmpty && !emptyPresent)
         { // add missing empty entry
-            addSelectItem(input, textResolver, new OptionEntry("", 
getNullText(ii)), 0);
+            addSelectItem(selectItemList, textResolver, new OptionEntry("", 
getNullText(ii)), exprType, 0);
         }
         // Are there any items left?
         while (oe != null)
         { // add missing item
             if (oe.isActive() || ObjectUtils.compareEqual(oe.getValue(), 
currentValue))
             { // add item
-                addSelectItem(input, textResolver, oe);
+                addSelectItem(selectItemList, textResolver, oe, exprType);
             }
             oe = (ioe.hasNext() ? ioe.next() : null);
         }
     }
     
-    @SuppressWarnings("unchecked")
-    public void addSelectItem(UIComponent input, TextResolver textResolver, 
OptionEntry oe, int pos)
+    protected List<SelectItem> getSelectItemList(UISelectOne input)
     {
         List<UIComponent> children = input.getChildren();
         // UISelectItems
-        UISelectItems items;
-        List<SelectItem> list;
         if (children.isEmpty())
-        {   // create and add UISelectItems
-            items = new UISelectItems();
-            children.add(items);
-            list = new ArrayList<SelectItem>();
-            items.setValue(list);
-        }
-        else
-        {   // use existing UISelectItems
-            items = ((UISelectItems) children.get(0));
-            list = ((List<SelectItem>) items.getValue());
+            children.add(new UISelectItems());
+        else if (children.size()>1 && !(children.get(1) instanceof 
UIParameter))
+            log.warn("Unexpected number of child items ({}) for 
SelectInputControl", children.size());
+        UISelectItems items = (UISelectItems) children.get(0);
+        // List<SelectItem>
+        @SuppressWarnings("unchecked")
+        List<SelectItem> selectItemList = (List<SelectItem>) items.getValue();
+        if (selectItemList==null)
+        {   selectItemList = new ArrayList<SelectItem>();
+            items.setValue(selectItemList);
         }
+        return selectItemList;
+    }
+    
+    public void addSelectItem(List<SelectItem> list, TextResolver 
textResolver, OptionEntry oe, Class<?> exprType, int pos)
+    {
         // set value
         Object value;
-        Class<?> exprType = 
(Class<?>)input.getAttributes().get(SelectInputControl.VALUE_EXPRESSION_TYPE);
         if (exprType!=null)
         { // Use formatted value
             value = formatInputValue(oe.getValue(), exprType);
@@ -364,9 +426,9 @@ public class SelectInputControl extends InputControl
             list.add(selectItem);
     }
 
-    public void addSelectItem(UIComponent input, TextResolver textResolver, 
OptionEntry e)
+    public void addSelectItem(List<SelectItem> list, TextResolver 
textResolver, OptionEntry e, Class<?> exprType)
     {
-        addSelectItem(input, textResolver, e, -1);
+        addSelectItem(list, textResolver, e, exprType, -1);
     }
     
     protected void setItemLabel(SelectItem si, TextResolver textResolver, 
OptionEntry oe)
diff --git a/empire-db/src/main/java/org/apache/empire/commons/ObjectUtils.java 
b/empire-db/src/main/java/org/apache/empire/commons/ObjectUtils.java
index b12228a7..79f8187d 100644
--- a/empire-db/src/main/java/org/apache/empire/commons/ObjectUtils.java
+++ b/empire-db/src/main/java/org/apache/empire/commons/ObjectUtils.java
@@ -19,6 +19,7 @@
 package org.apache.empire.commons;
 
 import java.io.Serializable;
+import java.lang.reflect.Array;
 import java.math.BigDecimal;
 import java.sql.Timestamp;
 import java.text.ParseException;
@@ -676,6 +677,27 @@ public final class ObjectUtils
         return values;
     }
     
+    /**
+     * Converts an Object array to a String array.
+     * @param objArray the object array to convert
+     * @param defValue default value which will be set for all null objects 
+     * @return the String array
+     */
+    public static String[] toStringArray(Object[] objArray, String defValue)
+    {
+        if (objArray==null)
+            return null;
+        String[] strArray = new String[objArray.length];
+        for (int i=0; i<objArray.length; i++)
+        {
+            if (objArray[i]!=null)
+                strArray[i]=objArray[i].toString();
+            else 
+                strArray[i]=defValue;
+        }
+        return strArray;
+    }
+    
     /**
      * Converts an array to a list
      * 
@@ -696,24 +718,26 @@ public final class ObjectUtils
     }
     
     /**
-     * Converts an Object array to a String array.
-     * @param objArray the object array to convert
-     * @param defValue default value which will be set for all null objects 
-     * @return the String array
+     * Converts a list to an array
+     * e.g.:
+     * MyItem[] array = ObjectUtils.listToArray(MyItem[].class, myList)
+     * 
+     * @param type the array type
+     * @param list the item list
+     * 
+     * @return the array
      */
-    public static String[] toStringArray(Object[] objArray, String defValue)
+    @SuppressWarnings("unchecked")
+    public static <T> T[] listToArray(Class<? extends T[]> type, List<T> list)
     {
-        if (objArray==null)
+        if (list==null)
             return null;
-        String[] strArray = new String[objArray.length];
-        for (int i=0; i<objArray.length; i++)
-        {
-            if (objArray[i]!=null)
-                strArray[i]=objArray[i].toString();
-            else 
-                strArray[i]=defValue;
-        }
-        return strArray;
+        T[] arr = ((Object)type == (Object)Object[].class)
+                ? (T[]) new Object[list.size()]
+                : (T[]) Array.newInstance(type.getComponentType(), 
list.size());
+        for (int i=0; i<arr.length; i++)
+            arr[i] = list.get(i);
+        return arr;
     }
 
     /**
diff --git a/empire-db/src/main/java/org/apache/empire/commons/Options.java 
b/empire-db/src/main/java/org/apache/empire/commons/Options.java
index d66a0068..962bc7a4 100644
--- a/empire-db/src/main/java/org/apache/empire/commons/Options.java
+++ b/empire-db/src/main/java/org/apache/empire/commons/Options.java
@@ -43,6 +43,12 @@ import org.w3c.dom.Element;
 public class Options extends AbstractSet<OptionEntry> implements Cloneable, 
Serializable
 {
        private static final long serialVersionUID = 1L;
+       
+       @FunctionalInterface
+       public interface OptionGroupResolver 
+       {
+           Object getGroup(OptionEntry oe);
+       }
 
     /**
      * Implements the Map interface for Options
@@ -154,6 +160,8 @@ public class Options extends AbstractSet<OptionEntry> 
implements Cloneable, Seri
 
     private final ArrayList<OptionEntry> list;
     
+    private OptionGroupResolver optionGroupResolver;
+    
     public Options()
     {   // Default constructor
         this.list = new ArrayList<OptionEntry>();
@@ -208,6 +216,27 @@ public class Options extends AbstractSet<OptionEntry> 
implements Cloneable, Seri
         return new Options(this);
     }
 
+    /**
+     * Returns the function that determines the group to which an option entry 
belongs.
+     * @return the group resolver function or null
+     */
+    public OptionGroupResolver getOptionGroupResolver()
+    {
+        return optionGroupResolver;
+    }
+
+    /**
+     * Sets a function that determines the group to which an option entry 
belongs.
+     * e.g.:
+     * options.setOptionGroupResolver((oe) -> 
((MyEnum)oe.getValue()).getCategory());
+     * 
+     * @param optionGroupResolver the group resolver function
+     */
+    public void setOptionGroupResolver(OptionGroupResolver optionGroupResolver)
+    {
+        this.optionGroupResolver = optionGroupResolver;
+    }
+
     protected int getIndex(Object value)
     {
         if (value instanceof OptionEntry)

Reply via email to