001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.lang.reflect.Array;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Properties;
031import java.util.Set;
032import java.util.function.Supplier;
033
034import org.w3c.dom.NodeList;
035
036/**
037 * A number of helper methods for working with collections
038 */
039public final class CollectionHelper {
040
041    /**
042     * Utility classes should not have a public constructor.
043     */
044    private CollectionHelper() {
045    }
046
047    /**
048     * Returns the size of the collection if it can be determined to be a collection
049     *
050     * @param  value the collection
051     * @return       the size, or <tt>null</tt> if not a collection
052     */
053    public static Integer size(Object value) {
054        if (value != null) {
055            if (value instanceof Collection<?> collection) {
056                return collection.size();
057            } else if (value instanceof Map<?, ?> map) {
058                return map.size();
059            } else if (value instanceof Object[] array) {
060                return array.length;
061            } else if (value.getClass().isArray()) {
062                return Array.getLength(value);
063            } else if (value instanceof NodeList nodeList) {
064                return nodeList.getLength();
065            }
066        }
067        return null;
068    }
069
070    /**
071     * Sets the value of the entry in the map for the given key, though if the map already contains a value for the
072     * given key then the value is appended to a list of values.
073     *
074     * @param map   the map to add the entry to
075     * @param key   the key in the map
076     * @param value the value to put in the map
077     */
078    @SuppressWarnings("unchecked")
079    public static void appendValue(Map<String, Object> map, String key, Object value) {
080        Object oldValue = map.get(key);
081        if (oldValue != null) {
082            List<Object> list;
083            if (oldValue instanceof List) {
084                list = (List<Object>) oldValue;
085            } else {
086                list = new ArrayList<>();
087                list.add(oldValue);
088                // replace old entry with list
089                map.remove(key);
090                map.put(key, list);
091            }
092            list.add(value);
093        } else {
094            map.put(key, value);
095        }
096    }
097
098    @SafeVarargs
099    public static <T> Set<T> createSetContaining(T... contents) {
100        return new HashSet<>(Arrays.asList(contents));
101    }
102
103    public static String collectionAsCommaDelimitedString(Collection<?> col) {
104        if (col == null || col.isEmpty()) {
105            return "";
106        }
107
108        StringBuilder sb = new StringBuilder();
109        Iterator<?> it = col.iterator();
110        while (it.hasNext()) {
111            sb.append(it.next().toString());
112            if (it.hasNext()) {
113                sb.append(",");
114            }
115        }
116
117        return sb.toString();
118    }
119
120    /**
121     * Traverses the given map recursively and flattern the keys by combining them with the optional separator.
122     *
123     * @param  map       the map
124     * @param  separator optional separator to use in key name, for example a hyphen or dot.
125     * @return           the map with flattern keys
126     */
127    public static Map<String, Object> flattenKeysInMap(Map<String, Object> map, String separator) {
128        Map<String, Object> answer = new LinkedHashMap<>();
129        doFlattenKeysInMap(map, "", ObjectHelper.isNotEmpty(separator) ? separator : "", answer);
130        return answer;
131    }
132
133    private static void doFlattenKeysInMap(
134            Map<String, Object> source, String prefix, String separator, Map<String, Object> target) {
135        for (Map.Entry<String, Object> entry : source.entrySet()) {
136            String key = entry.getKey();
137            Object value = entry.getValue();
138            String newKey = prefix.isEmpty() ? key : prefix + separator + key;
139
140            if (value instanceof Map map) {
141                doFlattenKeysInMap(map, newKey, separator, target);
142            } else {
143                target.put(newKey, value);
144            }
145        }
146    }
147
148    /**
149     * Build an unmodifiable map on top of a given map. Note tha thew given map is copied if not null.
150     *
151     * @param  map a map
152     * @return     an unmodifiable map.
153     */
154    public static <K, V> Map<K, V> unmodifiableMap(Map<K, V> map) {
155        return map == null
156                ? Collections.emptyMap()
157                : Collections.unmodifiableMap(new HashMap<>(map));
158    }
159
160    /**
161     * Build a map from varargs.
162     */
163    @SuppressWarnings("unchecked")
164    public static <K, V> Map<K, V> mapOf(Supplier<Map<K, V>> creator, K key, V value, Object... keyVals) {
165        Map<K, V> map = creator.get();
166        map.put(key, value);
167
168        for (int i = 0; i < keyVals.length; i += 2) {
169            map.put(
170                    (K) keyVals[i],
171                    (V) keyVals[i + 1]);
172        }
173
174        return map;
175    }
176
177    /**
178     * Build an immutable map from varargs.
179     */
180    public static <K, V> Map<K, V> immutableMapOf(Supplier<Map<K, V>> creator, K key, V value, Object... keyVals) {
181        return Collections.unmodifiableMap(
182                mapOf(creator, key, value, keyVals));
183    }
184
185    /**
186     * Build a map from varargs.
187     */
188    public static <K, V> Map<K, V> mapOf(K key, V value, Object... keyVals) {
189        return mapOf(HashMap::new, key, value, keyVals);
190    }
191
192    /**
193     * Build an immutable map from varargs.
194     */
195    public static <K, V> Map<K, V> immutableMapOf(K key, V value, Object... keyVals) {
196        return Collections.unmodifiableMap(
197                mapOf(HashMap::new, key, value, keyVals));
198    }
199
200    /**
201     * Build a {@link java.util.Properties} from varargs.
202     */
203    public static Properties propertiesOf(String key, String value, String... keyVals) {
204        Properties properties = new Properties();
205        properties.setProperty(key, value);
206
207        for (int i = 0; i < keyVals.length; i += 2) {
208            properties.setProperty(
209                    keyVals[i],
210                    keyVals[i + 1]);
211        }
212
213        return properties;
214    }
215
216    /**
217     * Build a new map that is the result of merging the given list of maps.
218     */
219    @SafeVarargs
220    public static <K, V> Map<K, V> mergeMaps(Map<K, V> map, Map<K, V>... maps) {
221        Map<K, V> answer = new HashMap<>();
222
223        if (map != null) {
224            answer.putAll(map);
225        }
226
227        for (Map<K, V> m : maps) {
228            answer.putAll(m);
229        }
230
231        return answer;
232    }
233
234    @SuppressWarnings("unchecked")
235    private static <T> Object addToList(Map<String, ? super Object> headers, String key, T value) {
236        Object existing = headers.get(key);
237        List<Object> list;
238        if (existing instanceof List) {
239            list = (List<Object>) existing;
240        } else {
241            list = new ArrayList<>();
242            list.add(existing);
243        }
244        list.add(value);
245        return list;
246    }
247
248    /**
249     * When trying to set the value for a map, if the value already exists, appends it to a list. Otherwise, sets the
250     * entry to the given value.
251     *
252     * @param headers the map that whose entry will be set or appended
253     * @param key     the key on the map
254     * @param value   the value to set or append within the map
255     */
256    public static <T> void appendEntry(Map<String, ? super Object> headers, String key, T value) {
257        if (headers.containsKey(key)) {
258            headers.put(key, addToList(headers, key, value));
259        } else {
260            headers.put(key, value);
261        }
262    }
263}