/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.metamodel.util; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Various utility methods for handling of collections and arrays. */ public final class CollectionUtils { private CollectionUtils() { // prevent instantiation } /** * Searches a map for a given key. The key can be a regular map key, or a * simple expression of the form: * * <ul> * <li>foo.bar (will lookup 'foo', and then 'bar' in a potential nested map) * </li> * <li>foo.bar[0].baz (will lookup 'foo', then 'bar' in a potential nested * map, then pick the first element in case it is a list/array and then pick * 'baz' from the potential map at that position). * </ul> * * @param map * the map to search in * @param key * the key to resolve * @return the object in the map with the given key/expression. Or null if * it does not exist. */ public static Object find(Map<?, ?> map, String key) { if (map == null || key == null) { return null; } final Object result = map.get(key); if (result == null) { return find(map, key, 0); } return result; } private static Object find(Map<?, ?> map, String key, int fromIndex) { final int indexOfDot = key.indexOf('.', fromIndex); final int indexOfBracket = key.indexOf('[', fromIndex); int indexOfEndBracket = -1; int arrayIndex = -1; boolean hasDot = indexOfDot != -1; boolean hasBracket = indexOfBracket != -1; if (hasBracket) { // also check that there is an end-bracket indexOfEndBracket = key.indexOf("]", indexOfBracket); hasBracket = indexOfEndBracket != -1; if (hasBracket) { final String indexString = key.substring(indexOfBracket + 1, indexOfEndBracket); try { arrayIndex = Integer.parseInt(indexString); } catch (NumberFormatException e) { // not a valid array/list index hasBracket = false; } } } if (hasDot && hasBracket) { if (indexOfDot > indexOfBracket) { hasDot = false; } else { hasBracket = false; } } if (hasDot) { final String prefix = key.substring(0, indexOfDot); final Object nestedObject = map.get(prefix); if (nestedObject == null) { return find(map, key, indexOfDot + 1); } if (nestedObject instanceof Map) { final String remainingPart = key.substring(indexOfDot + 1); @SuppressWarnings("unchecked") final Map<String, ?> nestedMap = (Map<String, ?>) nestedObject; return find(nestedMap, remainingPart); } } if (hasBracket) { final String prefix = key.substring(0, indexOfBracket); final Object nestedObject = map.get(prefix); if (nestedObject == null) { return find(map, key, indexOfBracket + 1); } String remainingPart = key.substring(indexOfEndBracket + 1); try { final Object valueAtIndex; if (nestedObject instanceof List) { valueAtIndex = ((List<?>) nestedObject).get(arrayIndex); } else if (nestedObject.getClass().isArray()) { valueAtIndex = Array.get(nestedObject, arrayIndex); } else { // no way to extract from a non-array and non-list valueAtIndex = null; } if (valueAtIndex != null) { if (remainingPart.startsWith(".")) { remainingPart = remainingPart.substring(1); } if (remainingPart.isEmpty()) { return valueAtIndex; } if (valueAtIndex instanceof Map) { @SuppressWarnings("unchecked") final Map<String, ?> nestedMap = (Map<String, ?>) valueAtIndex; return find(nestedMap, remainingPart); } else { // not traversing any further. Should we want to add // support for double-sided arrays, we could do it here. } } } catch (IndexOutOfBoundsException e) { return null; } } return null; } /** * Concatenates two arrays * * @param existingArray * an existing array * @param elements * the elements to add to the end of it * @return */ @SuppressWarnings("unchecked") public static <E> E[] array(final E[] existingArray, final E... elements) { if (existingArray == null) { return elements; } Object result = Array.newInstance(existingArray.getClass().getComponentType(), existingArray.length + elements.length); System.arraycopy(existingArray, 0, result, 0, existingArray.length); System.arraycopy(elements, 0, result, existingArray.length, elements.length); return (E[]) result; } public static <E> List<E> concat(boolean removeDuplicates, Collection<? extends E> firstCollection, Collection<?>... collections) { final List<E> result; if (removeDuplicates) { result = new ArrayList<E>(); addElements(removeDuplicates, result, firstCollection); } else { result = new ArrayList<E>(firstCollection); } for (Collection<?> collection : collections) { @SuppressWarnings("unchecked") Collection<? extends E> elems = (Collection<? extends E>) collection; addElements(removeDuplicates, result, elems); } return result; } private static <E> void addElements(boolean removeDuplicates, final List<E> result, Collection<? extends E> elements) { for (E item : elements) { if (removeDuplicates) { if (!result.contains(item)) { result.add(item); } } else { result.add(item); } } } public static <E> E[] arrayRemove(E[] array, E elementToRemove) { @SuppressWarnings("unchecked") E[] result = (E[]) arrayRemoveInternal(array, elementToRemove); return result; } public static Object arrayRemove(Object array, Object elementToRemove) { return arrayRemoveInternal(array, elementToRemove); } private static Object arrayRemoveInternal(Object array, Object elementToRemove) { boolean found = false; final int oldLength = Array.getLength(array); if (oldLength == 0) { return array; } final int newLength = oldLength - 1; final Object result = Array.newInstance(array.getClass().getComponentType(), newLength); int nextIndex = 0; for (int i = 0; i < oldLength; i++) { final Object e = Array.get(array, i); if (e.equals(elementToRemove)) { found = true; } else { if (nextIndex == newLength) { break; } Array.set(result, nextIndex, e); nextIndex++; } } if (!found) { return array; } return result; } @SuppressWarnings("unchecked") public static <E> E[] arrayOf(Class<E> elementClass, Object arrayOrElement) { if (arrayOrElement == null) { return null; } if (arrayOrElement.getClass().isArray()) { return (E[]) arrayOrElement; } Object result = Array.newInstance(elementClass, 1); Array.set(result, 0, arrayOrElement); return (E[]) result; } public static <E> List<E> filter(E[] items, Predicate<? super E> predicate) { return filter(Arrays.asList(items), predicate); } public static <E> List<E> filter(Iterable<E> items, Predicate<? super E> predicate) { List<E> result = new ArrayList<E>(); for (E e : items) { if (predicate.eval(e).booleanValue()) { result.add(e); } } return result; } public static <I, O> List<O> map(I[] items, Func<? super I, O> func) { return map(Arrays.asList(items), func); } public static <I, O> List<O> map(Iterable<I> items, Func<? super I, O> func) { List<O> result = new ArrayList<O>(); for (I item : items) { O output = func.eval(item); result.add(output); } return result; } public static <E> void forEach(E[] items, Action<? super E> action) { forEach(Arrays.asList(items), action); } public static <E> void forEach(Iterable<E> items, Action<? super E> action) { for (E item : items) { try { action.run(item); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new IllegalStateException("Action threw exception", e); } } } public static <E> boolean isNullOrEmpty(E[] arr) { return arr == null || arr.length == 0; } public static boolean isNullOrEmpty(Collection<?> col) { return col == null || col.isEmpty(); } /** * General purpose list converter method. Will convert arrays, collections, * iterables etc. into lists. * * If the argument is a single object (such as a String or a POJO) it will * be wrapped in a single-element list. * * Null will be converted to the empty list. * * @param obj * any object * @return a list representation of the object */ public static List<?> toList(Object obj) { final List<Object> result; if (obj == null) { result = Collections.emptyList(); } else if (obj instanceof List) { @SuppressWarnings("unchecked") List<Object> list = (List<Object>) obj; result = list; } else if (obj.getClass().isArray()) { int length = Array.getLength(obj); result = new ArrayList<Object>(length); for (int i = 0; i < length; i++) { result.add(Array.get(obj, i)); } } else if (obj instanceof Iterable) { result = new ArrayList<Object>(); for (Object item : (Iterable<?>) obj) { result.add(item); } } else if (obj instanceof Iterator) { result = new ArrayList<Object>(); Iterator<?> it = (Iterator<?>) obj; while (it.hasNext()) { result.add(it.next()); } } else { result = new ArrayList<Object>(1); result.add(obj); } return result; } }