/* * Copyright 2017 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 * * http://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.el.functions; import static java.lang.String.format; import static org.omnifaces.util.Utils.isEmpty; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.faces.model.DataModel; import org.omnifaces.model.IterableDataModel; import org.omnifaces.util.Json; import org.omnifaces.util.Utils; /** * <p> * Collection of EL functions for data conversion: <code>of:iterableToList()</code> (with alternative <code>of:iterableToModel</code>), * <code>of:setToList()</code>, <code>of:mapToList()</code>, <code>of:joinArray()</code>, <code>of:joinCollection()</code>, * <code>of:joinMap()</code>, <code>of:splitArray()</code>, <code>of:splitList()</code>, and <code>of:toJson()</code>. * <p> * Regarding the <code>of:xxxToList()</code> functions; <code><ui:repeat></code> (and <code><h:dataTable></code>) * doesn't support <code>Iterable</code> (such as <code>Set</code>) and <code>Map</code> directly, so those functions may be * handy for them. If however EL 2.2 is used, then e.g. <code>#{bean.set.toArray()}</code> (for {@link Collection} types) and * <code>#{bean.map.entrySet().toArray()}</code> could be used instead. * But if EL 2.2 is not supported or for a general <code>Iterable</code> the provided EL functions can be used. * <p> * The <code>of:joinXxx()</code> functions basically joins the elements of the array, collection or map to a string using the given separator. * This may be helpful if you want to display the contents of a collection as a commaseparated string without the need for an <code><ui:repeat></code>. * <p> * The <code>of:splitXxx()</code> functions basically splits an array or list into an array of subarrays or list of sublists of the given fragment size. * This may be helpful if you want to create a two-dimensional matrix of a fixed width based on a single-dimensional array or list. * <p> * The <code>of:toJson()</code> function encodes any object to a string in JSON format according the rules of * {@link Json#encode(Object)}. * <p> * Note that since JSF 2.2 it should be possible to use <code>#{bean.set}</code> directly in <code><h:dataTable></code>, but not * in <code><ui:repeat></code>. * The <code>of:setToList()</code> thus remains useful for JSF 2.0 and 2.1 in all cases, but is not needed for * <code><h:dataTable></code> in JSF 2.2. * * @author Bauke Scholtz * @author Arjan Tijms * @author Radu Creanga {@literal <rdcrng@gmail.com>} * @see IterableDataModel * @see Json */ public final class Converters { // Constants ------------------------------------------------------------------------------------------------------ private static final String ERROR_NOT_AN_ARRAY = "The given type '%s' is not an array at all."; private static final String ERROR_INVALID_FRAGMENT_SIZE = "The given fragment size '%s' must be at least 1."; // Constructors --------------------------------------------------------------------------------------------------- private Converters() { // Hide constructor. } // Utility -------------------------------------------------------------------------------------------------------- /** * Converts a <code>Set<E></code> to a <code>List<E></code>. Useful when you want to iterate over a * <code>Set</code> in for example <code><ui:repeat></code>. * @param <E> The generic set element type. * @param set The set to be converted to list of its entries. * @return The converted list. */ public static <E> List<E> setToList(Set<E> set) { if (set == null) { return null; } return new ArrayList<>(set); } /** * Converts a <code>Map<K, V></code> to a <code>List<Map.Entry<K, V>></code>. Useful when you want * to iterate over a <code>Map</code> in for example <code><ui:repeat></code>. Each of the entries has the * usual <code>getKey()</code> and <code>getValue()</code> methods. * @param <K> The generic map key type. * @param <V> The generic map value type. * @param map The map to be converted to list of its entries. * @return The converted list. */ public static <K, V> List<Map.Entry<K, V>> mapToList(Map<K, V> map) { if (map == null) { return null; } return new ArrayList<>(map.entrySet()); } /** * Converts a <code>Iterable<E></code> to a <code>List<E></code>. Useful when you want to iterate over an * <code>Iterable</code>, which includes any type of <code>Collection</code> (which includes e.g. a <code>Set</code>) * in for example <code><ui:repeat></code> and <code><h:dataTable></code>. * <p> * When iterating specifically over a Set using the above mentioned components {@link Converters#setToList(Set)} is * an alternative to this. * * @param <E> The generic iterable element type. * @param iterable The Iterable to be converted to a List. * @return The converted List. * * @since 1.5 */ public static <E> List<E> iterableToList(Iterable<E> iterable) { if (iterable == null) { return null; } return Utils.iterableToList(iterable); } /** * Converts an <code>Iterable<E></code> to a <code>DataModel<E></code>. Useful when you want to iterate over an * <code>Iterable</code>, which includes any type of <code>Collection</code> (which includes e.g. a <code>Set</code>) * in for example <code><ui:repeat></code> and <code><h:dataTable></code>. * <p> * When iterating specifically over a Set using the above mentioned components {@link Converters#setToList(Set)} is * an alternative to this. Use this for more general cases or when the exact collection type is unknown. * <p> * For those same components {@link Converters#iterableToList(Iterable)} is another alternative. Use this when * a DataModel is specifically needed. * * @param <E> The generic iterable element type. * @param iterable The Iterable to be converted to a DataModel. * @return The converted DataModel. * * @since 1.5 */ public static <E> DataModel<E> iterableToModel(Iterable<E> iterable) { if (iterable == null) { return null; } return new IterableDataModel<>(iterable); } /** * Joins all elements of the given array to a single string, separated by the given separator. * @param array The array to be joined. * @param separator The separator to be used. If null, then it defaults to empty string. * @return All elements of the given array as a single string, separated by the given separator. * @throws IllegalArgumentException When the given array is not an array at all. * @since 1.3 */ public static String joinArray(Object array, String separator) { if (array == null) { return null; } if (!array.getClass().isArray()) { throw new IllegalArgumentException(format(ERROR_NOT_AN_ARRAY, array.getClass())); } StringBuilder builder = new StringBuilder(); for (int i = 0; i < Array.getLength(array); i++) { if (i > 0 && separator != null) { builder.append(separator); } builder.append(Array.get(array, i)); } return builder.toString(); } /** * Joins all elements of the given collection to a single string, separated by the given separator. * @param <E> The generic collection element type. * @param collection The collection to be joined. * @param separator The separator to be used. If null, then it defaults to empty string. * @return All elements of the given collection as a single string, separated by the given separator. * @since 1.3 */ public static <E> String joinCollection(Collection<E> collection, String separator) { if (collection == null) { return null; } StringBuilder builder = new StringBuilder(); int i = 0; for (E element : collection) { if (i++ > 0 && separator != null) { builder.append(separator); } builder.append(element); } return builder.toString(); } /** * Joins all elements of the given map to a single string, separated by the given key-value pair separator and * entry separator. * @param <K> The generic map key type. * @param <V> The generic map value type. * @param map The map to be joined. * @param pairSeparator The key-value pair separator to be used. If null, then it defaults to empty string. * @param entrySeparator The entry separator to be used. If null, then it defaults to empty string. * @return All elements of the given map as a single string, separated by the given separators. * @since 1.3 */ public static <K, V> String joinMap(Map<K, V> map, String pairSeparator, String entrySeparator) { if (map == null) { return null; } StringBuilder builder = new StringBuilder(); int i = 0; for (Entry<K, V> entry : map.entrySet()) { if (i++ > 0 && entrySeparator != null) { builder.append(entrySeparator); } builder.append(entry.getKey()); if (pairSeparator != null) { builder.append(pairSeparator); } builder.append(entry.getValue()); } return builder.toString(); } /** * Splits the given array into an array of subarrays of the given fragment size. This is useful for creating nested * <code><ui:repeat></code> structures, for example, when positioning a list of items into a grid based * layout system such as Twitter Bootstrap. * @param array The array to be split. * @param fragmentSize The size of each subarray. * @return A new array consisting of subarrays of the given array. * @throws IllegalArgumentException When the fragment size is less than 1. * @since 1.6 */ public static Object[][] splitArray(Object array, int fragmentSize) { if (isEmpty(array)) { return new Object[0][]; } if (!array.getClass().isArray()) { throw new IllegalArgumentException(format(ERROR_NOT_AN_ARRAY, array.getClass())); } if (fragmentSize < 1) { throw new IllegalArgumentException(format(ERROR_INVALID_FRAGMENT_SIZE, fragmentSize)); } int sourceSize = Array.getLength(array); Object[][] arrays = new Object[(sourceSize + fragmentSize - 1) / fragmentSize][]; for (int i = 0, j = 0; i < sourceSize; i += fragmentSize, j++) { arrays[j] = new Object[Math.min(fragmentSize, sourceSize - i)]; System.arraycopy(array, i, arrays[j], 0, arrays[j].length); } return arrays; } /** * Splits the given list into a list of sublists of the given fragment size. This is useful for creating nested * <code><ui:repeat></code> structures, for example, when positioning a list of items into a grid based * layout system such as Twitter Bootstrap. * @param <E> The generic list element type. * @param list The list to be split. * @param fragmentSize The size of each sublist. * @return A new list consisting of sublists of the given list. * @throws IllegalArgumentException When the fragment size is less than 1. * @since 1.6 */ public static <E> List<List<E>> splitList(List<E> list, int fragmentSize) { if (isEmpty(list)) { return Collections.emptyList(); } if (fragmentSize < 1) { throw new IllegalArgumentException(format(ERROR_INVALID_FRAGMENT_SIZE, fragmentSize)); } int sourceSize = list.size(); List<List<E>> lists = new ArrayList<>((sourceSize + fragmentSize - 1) / fragmentSize); for (int i = 0; i < sourceSize; i += fragmentSize) { lists.add(list.subList(i, Math.min(i + fragmentSize, sourceSize))); } return lists; } /** * Encode given object as JSON. * Currently, this delegates directly to {@link Json#encode(Object)}. * @param object Object to be encoded as JSON. * @return The encoded JSON string. * @see Json#encode(Object) * @since 1.5 */ public static String toJson(Object object) { return Json.encode(object); } /** * Print the stack trace of the given exception. * @param exception The exception to print the stack trace for. * @return The printed stack trace. */ public static String printStackTrace(Throwable exception) { if (exception == null) { return null; } StringWriter stringWriter = new StringWriter(); exception.printStackTrace(new PrintWriter(stringWriter, true)); return stringWriter.toString(); } }