org.omnifaces.util.selectitems.SelectItemsCollector Maven / Gradle / Ivy
/*
* Copyright OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.util.selectitems;
import static java.lang.String.format;
import static org.omnifaces.util.Utils.isEmpty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UISelectItem;
import jakarta.faces.component.UISelectItems;
import jakarta.faces.context.FacesContext;
import jakarta.faces.model.SelectItem;
import jakarta.faces.model.SelectItemGroup;
import org.omnifaces.el.ScopedRunner;
import org.omnifaces.model.ExtendedSelectItem;
/**
* Collection of utility methods for collecting {@link SelectItem} instances from various sources.
*
* @author Arjan Tijms
*
*/
public final class SelectItemsCollector {
private static final String ERROR_UNKNOWN_SELECT_TYPE =
"A value expression of type '%s' is disallowed for a select item";
private SelectItemsCollector() {
//
}
/**
* This method gets all select items that are expressed via {@link UISelectItem} or {@link UISelectItems}
* children of the given parent component.
*
* Note that if {@link SelectItemGroup} instances are present then those will be inserted directly in the returned list
* and the using code still has to iterate over its children recursively to obtain all separate {@link SelectItem} instances.
*
* @param parent the parent whose children are scanned
* @param context The involved faces context.
* @return list of select items obtained from parent's children.
*/
public static List collectFromParent(FacesContext context, UIComponent parent) {
List selectItems = new ArrayList<>();
// Iterate over all children of the parent component. Non-UISelectItem/s children are automatically skipped.
for (UIComponent child : parent.getChildren()) {
if (child instanceof UISelectItem) {
UISelectItem uiSelectItem = (UISelectItem) child;
selectItems.add(getFromUISelectItem(uiSelectItem));
}
else if (child instanceof UISelectItems) {
UISelectItems uiSelectItems = (UISelectItems) child;
if (uiSelectItems.getValue() != null) {
selectItems.addAll(collectFromUISelectItems(context, uiSelectItems));
}
}
}
return selectItems;
}
private static SelectItem getFromUISelectItem(UISelectItem uiSelectItem) {
Object value = uiSelectItem.getValue();
if (value instanceof SelectItem) {
// A single SelectItem can be added directly without any further processing.
return (SelectItem)value;
}
else if (value == null) {
// No value binding specified, create a select item out of the properties of the UI component.
return new ExtendedSelectItem(uiSelectItem);
}
else {
// A value binding was specified, but of a type we don't support.
throw new IllegalArgumentException(format(ERROR_UNKNOWN_SELECT_TYPE, value.getClass().toString()));
}
}
private static Collection collectFromUISelectItems(FacesContext context, UISelectItems uiSelectItems) {
Object value = uiSelectItems.getValue();
if (value instanceof SelectItem) {
// A single SelectItem can be added directly without any further processing.
return Collections.singleton((SelectItem) value);
}
else if (value instanceof Object[]) {
// An array of objects is supposed to be transformed by the SelectItems iteration construct.
return collectFromUISelectItemsIterator(context, uiSelectItems, Arrays.asList((Object[]) value));
}
else if (value instanceof Iterable) {
// An iterable (Collection, List, etc) is also supposed to be transformed by the SelectItems iteration construct.
return collectFromUISelectItemsIterator(context, uiSelectItems, (Iterable>) value);
}
else if (value instanceof Map) {
// A map has its own algorithm for how it should be turned into a list of SelectItems.
return SelectItemsBuilder.fromMap((Map, ?>)value);
}
else {
// A value binding was specified, but of a type we don't support.
throw new IllegalArgumentException(format(ERROR_UNKNOWN_SELECT_TYPE, value.getClass().toString()));
}
}
/**
* This method runs the algorithm expressed by a UISelectItems
component that uses the var
iterator construct to generate
* a list of SelectItem
s.
*
* @param uiSelectItems The involved select items component.
* @param items The available select items.
* @param facesContext The involved faces context.
* @return list of SelectItem
obtained from the given parameters
*/
public static List collectFromUISelectItemsIterator(FacesContext facesContext, UISelectItems uiSelectItems, Iterable> items) {
List selectItems = new ArrayList<>();
Map attributes = uiSelectItems.getAttributes();
String var = (String) attributes.get("var");
// Helper class that's used to set the item value in (EL) scope using the name set by "var" during the iteration.
// If during each iteration the value of this is changed, any value expressions in the attribute
// map referring it will resolve to that particular instance.
ScopedRunner scopedRunner = new ScopedRunner(facesContext);
for (Object item : items) {
// If the item is already a SelectItem, take it directly.
// NOTE: I'm not 100% sure if this is right, since it now allows a collection to consist
// out of a mix of SelectItems and non-SelectItems. Should we maybe always process the iterator
// if there's a "var", "itemLabel" or "itemValue" present, or should we process the entire collection
// as SelectItems if the first element is a SelectItem and throw an exception as soon as we encounter
// a non-SelectItem?
if (item instanceof SelectItem) {
selectItems.add((SelectItem)item);
continue;
}
if (!isEmpty(var)) {
scopedRunner.with(var, item);
}
// During each iteration, just resolve all attributes again.
scopedRunner.invoke(() -> {
Object itemValue = getItemValue(attributes, item);
Object noSelectionValue = attributes.get("noSelectionValue");
boolean itemValueIsNoSelectionValue = noSelectionValue != null && noSelectionValue.equals(itemValue);
selectItems.add(new SelectItem(
itemValue,
getItemLabel(attributes, itemValue),
getItemDescription(attributes),
getBooleanAttribute(attributes, "itemDisabled", false),
getBooleanAttribute(attributes, "itemLabelEscaped", true),
getBooleanAttribute(attributes, "noSelectionOption", false) || itemValueIsNoSelectionValue
));
});
}
return selectItems;
}
/**
* Gets the optional value. It defaults to the item itself if not specified.
*
* @param attributes the attributes from which the label is fetched.
* @param item default value if no item value present
* @return the value, or the item if none is present
*/
private static Object getItemValue(Map attributes, Object item) {
Object itemValue = attributes.get("itemValue");
if (itemValue == null) {
itemValue = item;
}
return itemValue;
}
/**
* Gets the optional label. It defaults to the item value if not specified.
*
* @param attributes the attributes from which the label is fetched.
* @param itemValue default value if no item value present
* @return the label, or the item value if none present
*/
private static String getItemLabel(Map attributes, Object itemValue) {
Object itemLabelObj = attributes.get("itemLabel");
String itemLabel;
if (itemLabelObj != null) {
itemLabel = itemLabelObj.toString();
}
else {
itemLabel = itemValue.toString();
}
return itemLabel;
}
/**
* Gets the optional description.
*
* @param attributes the attributes from which the description is fetched.
* @return the description, or null if none present.
*/
private static String getItemDescription(Map attributes) {
Object itemDescriptionObj = attributes.get("itemDescription");
String itemDescription = null;
if (itemDescriptionObj != null) {
itemDescription = itemDescriptionObj.toString();
}
return itemDescription;
}
/**
* Gets the name boolean attribute. It defaults to false
if not specified.
* @param attributes the attributes from which the attribute is fetched.
* @param key name of the attribute
* @return the boolean represented by the attribute or false if there's no such attribute
*/
private static boolean getBooleanAttribute(Map attributes, String key, boolean defaultValue) {
Object valueObj = attributes.get(key);
boolean value = defaultValue;
if (valueObj != null) {
value = Boolean.parseBoolean(valueObj.toString());
}
return value;
}
}